perf: 修改 CommandFilterACL, CommandGroup Model 的 Meta 内部类; 修改 Command Model 的 system_user -> account 字段; 修改 ConnectionToken 的 command_filter_acls 返回字段;

pull/9155/head^2
Bai 2022-12-05 13:27:51 +08:00
parent 0cfcfacb6d
commit cc7424dbfe
16 changed files with 106 additions and 60 deletions

View File

@ -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'},
),
]

View File

@ -83,6 +83,7 @@ class BaseACL(CommonModelMixin):
objects = ACLManager.from_queryset(BaseACLQuerySet)() objects = ACLManager.from_queryset(BaseACLQuerySet)()
class Meta: class Meta:
ordering = ('priority', 'name')
abstract = True abstract = True
@ -96,7 +97,8 @@ class UserAssetAccountBaseACL(BaseACL, OrgModelMixin):
objects = ACLManager.from_queryset(UserAssetAccountACLQuerySet)() objects = ACLManager.from_queryset(UserAssetAccountACLQuerySet)()
class Meta: class Meta(BaseACL.Meta):
unique_together = ('name', 'org_id')
abstract = True abstract = True
@classmethod @classmethod
@ -118,4 +120,3 @@ class UserAssetAccountBaseACL(BaseACL, OrgModelMixin):
if kwargs: if kwargs:
queryset = queryset.filter(**kwargs) queryset = queryset.filter(**kwargs)
return queryset return queryset

View File

@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _
from common.utils import lazyproperty, get_logger from common.utils import lazyproperty, get_logger
from orgs.mixins.models import JMSOrgBaseModel from orgs.mixins.models import JMSOrgBaseModel
from .base import UserAssetAccountBaseACL, UserAssetAccountACLQuerySet, ACLManager from .base import UserAssetAccountBaseACL
logger = get_logger(__file__) logger = get_logger(__file__)
@ -94,23 +94,11 @@ class CommandGroup(JMSOrgBaseModel):
return '{} % {}'.format(self.name, self.type) 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): class CommandFilterACL(UserAssetAccountBaseACL):
command_groups = models.ManyToManyField(CommandGroup, verbose_name=_('Commands')) command_groups = models.ManyToManyField(CommandGroup, verbose_name=_('Commands'))
objects = ACLManager.from_queryset(CommandFilterACLQuerySet)() class Meta(UserAssetAccountBaseACL.Meta):
abstract = False
class Meta:
unique_together = ('name', 'org_id')
ordering = ('priority', '-date_updated', 'name')
verbose_name = _('Command acl') verbose_name = _('Command acl')
def __str__(self): def __str__(self):

View File

@ -15,9 +15,9 @@ class LoginACL(BaseACL):
# 规则, ip_group, time_period # 规则, ip_group, time_period
rules = models.JSONField(default=dict, verbose_name=_('Rule')) rules = models.JSONField(default=dict, verbose_name=_('Rule'))
class Meta: class Meta(BaseACL.Meta):
ordering = ('priority', '-date_updated', 'name')
verbose_name = _('Login acl') verbose_name = _('Login acl')
abstract = False
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -6,10 +6,9 @@ from .base import UserAssetAccountBaseACL
class LoginAssetACL(UserAssetAccountBaseACL): class LoginAssetACL(UserAssetAccountBaseACL):
class Meta: class Meta(UserAssetAccountBaseACL.Meta):
unique_together = ('name', 'org_id')
ordering = ('priority', '-date_updated', 'name')
verbose_name = _('Login asset acl') verbose_name = _('Login asset acl')
abstract = False
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -160,15 +160,15 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel):
return self.domain.random_gateway() return self.domain.random_gateway()
@lazyproperty @lazyproperty
def acl_command_groups(self): def command_filter_acls(self):
from acls.models import CommandFilterACL from acls.models import CommandFilterACL
kwargs = { kwargs = {
'user': self.user, 'user': self.user,
'asset': self.asset, 'asset': self.asset,
'account': self.account, 'account': self.account,
} }
command_groups = CommandFilterACL.filter_queryset(**kwargs).get_command_groups() acls = CommandFilterACL.filter_queryset(**kwargs).valid()
return command_groups return acls
class SuperConnectionToken(ConnectionToken): class SuperConnectionToken(ConnectionToken):

View File

@ -1,13 +1,15 @@
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 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 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__ = [ __all__ = [
'ConnectionTokenSerializer', 'ConnectionTokenSecretSerializer', 'ConnectionTokenSerializer', 'ConnectionTokenSecretSerializer',
@ -125,13 +127,20 @@ class ConnectionTokenGatewaySerializer(serializers.ModelSerializer):
] ]
class ConnectionTokenACLCmdGroupSerializer(serializers.ModelSerializer): class ConnectionTokenACLSerializer(serializers.ModelSerializer):
""" ACL command group""" 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: class Meta:
model = CommandGroup model = CommandFilterACL
fields = [ 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) account = ConnectionTokenAccountSerializer(read_only=True)
gateway = ConnectionTokenGatewaySerializer(read_only=True) gateway = ConnectionTokenGatewaySerializer(read_only=True)
platform = ConnectionTokenPlatform(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() actions = ActionChoicesField()
expire_at = serializers.IntegerField() expire_at = serializers.IntegerField()
expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True) expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True)
@ -160,7 +169,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin):
model = ConnectionToken model = ConnectionToken
fields = [ fields = [
'id', 'value', 'user', 'asset', 'account', 'platform', 'id', 'value', 'user', 'asset', 'account', 'platform',
'acl_command_groups', 'command_filter_acls',
'protocol', 'gateway', 'actions', 'expire_at', 'expire_now', 'protocol', 'gateway', 'actions', 'expire_at', 'expire_now',
] ]
extra_kwargs = { extra_kwargs = {

View File

@ -26,7 +26,7 @@ __all__ = ['CommandViewSet', 'InsecureCommandAlertAPI']
class CommandQueryMixin: class CommandQueryMixin:
command_store = get_command_storage() command_store = get_command_storage()
filterset_fields = [ filterset_fields = [
"asset", "system_user", "user", "session", "asset", "account", "user", "session",
"risk_level", "input" "risk_level", "input"
] ]
default_days_ago = 5 default_days_ago = 5
@ -56,7 +56,7 @@ class CommandQueryMixin:
multi_command_storage = get_multi_command_storage() multi_command_storage = get_multi_command_storage()
queryset = multi_command_storage.filter( queryset = multi_command_storage.filter(
date_from=date_from, date_to=date_to, 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')), 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(), risk_level=self.get_query_risk_level(), org_id=self.get_org_id(),
) )
@ -91,7 +91,7 @@ class CommandViewSet(JMSBulkModelViewSet):
{ {
"user": "admin", "user": "admin",
"asset": "localhost", "asset": "localhost",
"system_user": "web", "account": "web",
"session": "xxxxxx", "session": "xxxxxx",
"input": "whoami", "input": "whoami",
"output": "d2hvbWFp", # base64.b64encode(s) "output": "d2hvbWFp", # base64.b64encode(s)

View File

@ -15,13 +15,13 @@ class CommandBase(object):
@abc.abstractmethod @abc.abstractmethod
def filter(self, date_from=None, date_to=None, 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): input=None, session=None, risk_level=None, org_id=None):
pass pass
@abc.abstractmethod @abc.abstractmethod
def count(self, date_from=None, date_to=None, 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): input=None, session=None):
pass pass

View File

@ -21,7 +21,7 @@ class CommandStore(CommandBase):
""" """
self.model.objects.create( self.model.objects.create(
user=command["user"], asset=command["asset"], 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"], output=command["output"], session=command["session"],
risk_level=command.get("risk_level", 0), org_id=command["org_id"], risk_level=command.get("risk_level", 0), org_id=command["org_id"],
timestamp=command["timestamp"] timestamp=command["timestamp"]
@ -36,7 +36,7 @@ class CommandStore(CommandBase):
cmd_input = pretty_string(c['input']) cmd_input = pretty_string(c['input'])
cmd_output = pretty_string(c['output'], max_length=1024) cmd_output = pretty_string(c['output'], max_length=1024)
_commands.append(self.model( _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"], input=cmd_input, output=cmd_output, session=c["session"],
risk_level=c.get("risk_level", 0), org_id=c["org_id"], risk_level=c.get("risk_level", 0), org_id=c["org_id"],
timestamp=c["timestamp"] timestamp=c["timestamp"]
@ -64,7 +64,7 @@ class CommandStore(CommandBase):
@staticmethod @staticmethod
def make_filter_kwargs( def make_filter_kwargs(
date_from=None, date_to=None, 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): input=None, session=None, risk_level=None, org_id=None):
filter_kwargs = {} filter_kwargs = {}
date_from_default = timezone.now() - datetime.timedelta(days=7) date_from_default = timezone.now() - datetime.timedelta(days=7)
@ -87,8 +87,8 @@ class CommandStore(CommandBase):
filter_kwargs["user__startswith"] = user filter_kwargs["user__startswith"] = user
if asset: if asset:
filter_kwargs['asset'] = asset filter_kwargs['asset'] = asset
if system_user: if account:
filter_kwargs['system_user'] = system_user filter_kwargs['account'] = account
if input: if input:
filter_kwargs['input__icontains'] = input filter_kwargs['input__icontains'] = input
if session: if session:
@ -100,22 +100,22 @@ class CommandStore(CommandBase):
return filter_kwargs return filter_kwargs
def filter(self, date_from=None, date_to=None, 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): input=None, session=None, risk_level=None, org_id=None):
filter_kwargs = self.make_filter_kwargs( filter_kwargs = self.make_filter_kwargs(
date_from=date_from, date_to=date_to, user=user, 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, session=session, risk_level=risk_level, org_id=org_id,
) )
queryset = self.model.objects.filter(**filter_kwargs) queryset = self.model.objects.filter(**filter_kwargs)
return queryset return queryset
def count(self, date_from=None, date_to=None, 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): input=None, session=None):
filter_kwargs = self.make_filter_kwargs( filter_kwargs = self.make_filter_kwargs(
date_from=date_from, date_to=date_to, user=user, 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, session=session,
) )
count = self.model.objects.filter(**filter_kwargs).count() count = self.model.objects.filter(**filter_kwargs).count()

View File

@ -49,7 +49,7 @@ class CommandStore(object):
self.es = Elasticsearch(hosts=hosts, max_retries=0, **kwargs) self.es = Elasticsearch(hosts=hosts, max_retries=0, **kwargs)
self.exact_fields = set() 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'} may_exact_fields = {'session', 'org_id'}
if self.is_new_index_type(): if self.is_new_index_type():
@ -142,7 +142,7 @@ class CommandStore(object):
def make_data(command): def make_data(command):
data = dict( data = dict(
user=command["user"], asset=command["asset"], 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"], output=command["output"], risk_level=command["risk_level"],
session=command["session"], timestamp=command["timestamp"], session=command["session"], timestamp=command["timestamp"],
org_id=command["org_id"] org_id=command["org_id"]

View File

@ -19,7 +19,7 @@ class AbstractSessionCommand(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=64, db_index=True, verbose_name=_("User")) user = models.CharField(max_length=64, db_index=True, verbose_name=_("User"))
asset = models.CharField(max_length=128, db_index=True, verbose_name=_("Asset")) 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")) input = models.CharField(max_length=128, db_index=True, verbose_name=_("Input"))
output = models.CharField(max_length=1024, blank=True, verbose_name=_("Output")) output = models.CharField(max_length=1024, blank=True, verbose_name=_("Output"))
session = models.CharField(max_length=36, db_index=True, verbose_name=_("Session")) session = models.CharField(max_length=36, db_index=True, verbose_name=_("Session"))

View File

@ -33,7 +33,8 @@ class SessionCommandSerializer(SimpleSessionCommandSerializer):
"""使用这个类作为基础Command Log Serializer类, 用来序列化""" """使用这个类作为基础Command Log Serializer类, 用来序列化"""
id = serializers.UUIDField(read_only=True) 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")) output = serializers.CharField(max_length=2048, allow_blank=True, label=_("Output"))
risk_level_display = serializers.SerializerMethodField(label=_('Risk level display')) risk_level_display = serializers.SerializerMethodField(label=_('Risk level display'))
timestamp = serializers.IntegerField(label=_('Timestamp')) timestamp = serializers.IntegerField(label=_('Timestamp'))
@ -45,7 +46,7 @@ class SessionCommandSerializer(SimpleSessionCommandSerializer):
risk_mapper = dict(AbstractSessionCommand.RISK_LEVEL_CHOICES) risk_mapper = dict(AbstractSessionCommand.RISK_LEVEL_CHOICES)
return risk_mapper.get(obj.risk_level) return risk_mapper.get(obj.risk_level)
def validate_system_user(self, value): def validate_account(self, value):
if len(value) > 64: if len(value) > 64:
value = pretty_string(value, 64) value = pretty_string(value, 64)
return value return value

View File

@ -16,7 +16,7 @@ class CommandFilter(filters.FilterSet):
class Meta: class Meta:
model = Command model = Command
fields = [ 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', 'date_from', 'date_to', 'session_id', 'risk_level', 'command_storage_id',
] ]
@ -49,14 +49,14 @@ class CommandFilter(filters.FilterSet):
class CommandFilterForStorageTree(CommandFilter): class CommandFilterForStorageTree(CommandFilter):
asset = filters.CharFilter(method='do_nothing') 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') session = filters.CharFilter(method='do_nothing')
risk_level = filters.NumberFilter(method='do_nothing') risk_level = filters.NumberFilter(method='do_nothing')
class Meta: class Meta:
model = CommandStorage model = CommandStorage
fields = [ 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', 'date_from', 'date_to', 'session_id', 'risk_level', 'command_storage_id',
] ]

View File

@ -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'),
),
]

View File

@ -33,7 +33,7 @@ class Command(AbstractSessionCommand):
cls(**{ cls(**{
'user': random_string(6), 'user': random_string(6),
'asset': random_string(10), 'asset': random_string(10),
'system_user': random_string(6), 'account': random_string(6),
'session': str(uuid.uuid4()), 'session': str(uuid.uuid4()),
'input': random_string(16), 'input': random_string(16),
'output': random_string(64), 'output': random_string(64),