perf: audits

pull/9044/head
feng 2022-11-10 14:44:23 +08:00
parent 644f3f1783
commit ba3f2099e6
8 changed files with 93 additions and 106 deletions

View File

@ -3,7 +3,7 @@ from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from orgs.models import Organization from orgs.models import Organization
from assets.const import Protocol from common.drf.fields import LabeledChoiceField
from acls import models from acls import models
@ -59,7 +59,7 @@ class LoginAssetACLSerializer(BulkOrgResourceModelSerializer):
assets = LoginAssetACLAssestsSerializer() assets = LoginAssetACLAssestsSerializer()
accounts = LoginAssetACLAccountsSerializer() accounts = LoginAssetACLAccountsSerializer()
reviewers_amount = serializers.IntegerField(read_only=True, source='reviewers.count') reviewers_amount = serializers.IntegerField(read_only=True, source='reviewers.count')
action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action')) action = LabeledChoiceField(choices=models.LoginAssetACL.ActionChoices.choices, label=_('Action'))
class Meta: class Meta:
model = models.LoginAssetACL model = models.LoginAssetACL
@ -67,7 +67,7 @@ class LoginAssetACLSerializer(BulkOrgResourceModelSerializer):
fields_small = fields_mini + [ fields_small = fields_mini + [
'users', 'accounts', 'assets', 'users', 'accounts', 'assets',
'is_active', 'date_created', 'date_updated', 'is_active', 'date_created', 'date_updated',
'priority', 'action', 'action_display', 'comment', 'created_by', 'org_id' 'priority', 'action', 'comment', 'created_by', 'org_id'
] ]
fields_m2m = ['reviewers', 'reviewers_amount'] fields_m2m = ['reviewers', 'reviewers_amount']
fields = fields_small + fields_m2m fields = fields_small + fields_m2m

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models import TextChoices, IntegerChoices
DEFAULT_CITY = _("Unknown") DEFAULT_CITY = _("Unknown")
@ -22,3 +23,37 @@ MODELS_NEED_RECORD = (
# xpack # xpack
'License', 'Account', 'SyncInstanceTask', 'ChangeAuthPlan', 'GatherUserTask', 'License', 'Account', 'SyncInstanceTask', 'ChangeAuthPlan', 'GatherUserTask',
) )
class OperateChoices(TextChoices):
mkdir = 'mkdir', _('Mkdir')
rmdir = 'rmdir', _('Rmdir')
delete = 'delete', _('Delete')
upload = 'upload', _('Upload')
rename = 'rename', _('Rename')
symlink = 'symlink', _('Symlink')
download = 'download', _('Download')
class ActionChoices(TextChoices):
view = 'view', _('View')
update = 'update', _('Update')
delete = 'delete', _('Delete')
create = 'create', _('Create')
class LoginTypeChoices(TextChoices):
web = 'W', _('Web')
terminal = 'T', _('Terminal')
unknown = 'U', _('Unknown')
class MFAChoices(IntegerChoices):
disabled = 0, _('Disabled')
enabled = 1, _('Enabled')
unknown = 2, _('-')
class LoginStatusChoices(IntegerChoices):
success = True, _('Success')
failed = False, _('Failed')

View File

@ -8,6 +8,9 @@ from common.utils import lazyproperty
from orgs.mixins.models import OrgModelMixin, Organization from orgs.mixins.models import OrgModelMixin, Organization
from orgs.utils import current_org from orgs.utils import current_org
from .const import (
OperateChoices, ActionChoices, LoginTypeChoices, MFAChoices, LoginStatusChoices
)
__all__ = [ __all__ = [
'FTPLog', 'OperateLog', 'PasswordChangeLog', 'UserLoginLog', 'FTPLog', 'OperateLog', 'PasswordChangeLog', 'UserLoginLog',
@ -15,30 +18,12 @@ __all__ = [
class FTPLog(OrgModelMixin): class FTPLog(OrgModelMixin):
OPERATE_DELETE = 'Delete'
OPERATE_UPLOAD = 'Upload'
OPERATE_DOWNLOAD = 'Download'
OPERATE_RMDIR = 'Rmdir'
OPERATE_RENAME = 'Rename'
OPERATE_MKDIR = 'Mkdir'
OPERATE_SYMLINK = 'Symlink'
OPERATE_CHOICES = (
(OPERATE_DELETE, _('Delete')),
(OPERATE_UPLOAD, _('Upload')),
(OPERATE_DOWNLOAD, _('Download')),
(OPERATE_RMDIR, _('Rmdir')),
(OPERATE_RENAME, _('Rename')),
(OPERATE_MKDIR, _('Mkdir')),
(OPERATE_SYMLINK, _('Symlink'))
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=128, verbose_name=_('User')) user = models.CharField(max_length=128, verbose_name=_('User'))
remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True) remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True)
asset = models.CharField(max_length=1024, verbose_name=_("Asset")) asset = models.CharField(max_length=1024, verbose_name=_("Asset"))
system_user = models.CharField(max_length=128, verbose_name=_("System user")) system_user = models.CharField(max_length=128, verbose_name=_("System user"))
operate = models.CharField(max_length=16, verbose_name=_("Operate"), choices=OPERATE_CHOICES) operate = models.CharField(max_length=16, verbose_name=_("Operate"), choices=OperateChoices.choices)
filename = models.CharField(max_length=1024, verbose_name=_("Filename")) filename = models.CharField(max_length=1024, verbose_name=_("Filename"))
is_success = models.BooleanField(default=True, verbose_name=_("Success")) is_success = models.BooleanField(default=True, verbose_name=_("Success"))
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start')) date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start'))
@ -48,19 +33,9 @@ class FTPLog(OrgModelMixin):
class OperateLog(OrgModelMixin): class OperateLog(OrgModelMixin):
ACTION_CREATE = 'create'
ACTION_VIEW = 'view'
ACTION_UPDATE = 'update'
ACTION_DELETE = 'delete'
ACTION_CHOICES = (
(ACTION_CREATE, _("Create")),
(ACTION_VIEW, _("View")),
(ACTION_UPDATE, _("Update")),
(ACTION_DELETE, _("Delete"))
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=128, verbose_name=_('User')) user = models.CharField(max_length=128, verbose_name=_('User'))
action = models.CharField(max_length=16, choices=ACTION_CHOICES, verbose_name=_("Action")) action = models.CharField(max_length=16, choices=ActionChoices.choices, verbose_name=_("Action"))
resource_type = models.CharField(max_length=64, verbose_name=_("Resource Type")) resource_type = models.CharField(max_length=64, verbose_name=_("Resource Type"))
resource = models.CharField(max_length=128, verbose_name=_("Resource")) resource = models.CharField(max_length=128, verbose_name=_("Resource"))
remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True) remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True)
@ -97,35 +72,17 @@ class PasswordChangeLog(models.Model):
class UserLoginLog(models.Model): class UserLoginLog(models.Model):
LOGIN_TYPE_CHOICE = (
('W', 'Web'),
('T', 'Terminal'),
('U', 'Unknown'),
)
MFA_DISABLED = 0
MFA_ENABLED = 1
MFA_UNKNOWN = 2
MFA_CHOICE = (
(MFA_DISABLED, _('Disabled')),
(MFA_ENABLED, _('Enabled')),
(MFA_UNKNOWN, _('-')),
)
STATUS_CHOICE = (
(True, _('Success')),
(False, _('Failed'))
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
username = models.CharField(max_length=128, verbose_name=_('Username')) username = models.CharField(max_length=128, verbose_name=_('Username'))
type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type')) type = models.CharField(choices=LoginTypeChoices.choices, max_length=2, verbose_name=_('Login type'))
ip = models.GenericIPAddressField(verbose_name=_('Login ip')) ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city')) city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city'))
user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent')) user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent'))
mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA')) mfa = models.SmallIntegerField(default=MFAChoices.unknown, choices=MFAChoices.choices, verbose_name=_('MFA'))
reason = models.CharField(default='', max_length=128, blank=True, verbose_name=_('Reason')) reason = models.CharField(default='', max_length=128, blank=True, verbose_name=_('Reason'))
status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status')) status = models.BooleanField(
default=LoginStatusChoices.success, choices=LoginStatusChoices.choices, verbose_name=_('Status')
)
datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login')) datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login'))
backend = models.CharField(max_length=32, default='', verbose_name=_('Authentication backend')) backend = models.CharField(max_length=32, default='', verbose_name=_('Authentication backend'))

View File

@ -3,39 +3,39 @@
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.drf.serializers import BulkSerializerMixin from common.drf.fields import LabeledChoiceField
from terminal.models import Session from terminal.models import Session
from . import models from . import models
from .const import (
ActionChoices, OperateChoices, MFAChoices, LoginStatusChoices, LoginTypeChoices
)
class FTPLogSerializer(serializers.ModelSerializer): class FTPLogSerializer(serializers.ModelSerializer):
operate_display = serializers.ReadOnlyField(source='get_operate_display', label=_('Operate display')) operate = LabeledChoiceField(choices=OperateChoices.choices, label=_('Operate'))
class Meta: class Meta:
model = models.FTPLog model = models.FTPLog
fields_mini = ['id'] fields_mini = ['id']
fields_small = fields_mini + [ fields_small = fields_mini + [
'user', 'remote_addr', 'asset', 'system_user', 'org_id', 'user', 'remote_addr', 'asset', 'system_user', 'org_id',
'operate', 'filename', 'operate_display', 'operate', 'filename', 'is_success', 'date_start',
'is_success',
'date_start',
] ]
fields = fields_small fields = fields_small
class UserLoginLogSerializer(serializers.ModelSerializer): class UserLoginLogSerializer(serializers.ModelSerializer):
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) mfa = LabeledChoiceField(choices=MFAChoices.choices, label=_('MFA'))
status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status display')) type = LabeledChoiceField(choices=LoginTypeChoices.choices, label=_('Type'))
mfa_display = serializers.ReadOnlyField(source='get_mfa_display', label=_('MFA display')) status = LabeledChoiceField(choices=LoginStatusChoices.choices, label=_('Status'))
class Meta: class Meta:
model = models.UserLoginLog model = models.UserLoginLog
fields_mini = ['id'] fields_mini = ['id']
fields_small = fields_mini + [ fields_small = fields_mini + [
'username', 'type', 'type_display', 'ip', 'city', 'user_agent', 'username', 'type', 'ip', 'city', 'user_agent',
'mfa', 'mfa_display', 'reason', 'reason_display', 'backend', 'backend_display', 'mfa', 'reason', 'reason_display', 'backend',
'status', 'status_display', 'backend_display', 'status', 'datetime',
'datetime',
] ]
fields = fields_small fields = fields_small
extra_kwargs = { extra_kwargs = {
@ -46,15 +46,14 @@ class UserLoginLogSerializer(serializers.ModelSerializer):
class OperateLogSerializer(serializers.ModelSerializer): class OperateLogSerializer(serializers.ModelSerializer):
action_display = serializers.CharField(source='get_action_display', label=_('Action')) action = LabeledChoiceField(choices=ActionChoices.choices, label=_('Action'))
class Meta: class Meta:
model = models.OperateLog model = models.OperateLog
fields_mini = ['id'] fields_mini = ['id']
fields_small = fields_mini + [ fields_small = fields_mini + [
'user', 'action', 'action_display', 'user', 'action', 'resource_type', 'resource_type_display',
'resource_type', 'resource_type_display', 'resource', 'resource', 'remote_addr', 'datetime', 'org_id'
'remote_addr', 'datetime', 'org_id'
] ]
fields = fields_small fields = fields_small
extra_kwargs = { extra_kwargs = {

View File

@ -1,38 +1,34 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import time
from django.db.models.signals import (
post_save, m2m_changed, pre_delete
)
from django.dispatch import receiver
from django.conf import settings from django.conf import settings
from django.db import transaction from django.db import transaction
from django.utils import timezone from django.dispatch import receiver
from django.utils import timezone, translation
from django.utils.functional import LazyObject from django.utils.functional import LazyObject
from django.contrib.auth import BACKEND_SESSION_KEY from django.contrib.auth import BACKEND_SESSION_KEY
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import translation from django.db.models.signals import post_save, m2m_changed, pre_delete
from rest_framework.renderers import JSONRenderer
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.renderers import JSONRenderer
from assets.models import Asset
from authentication.signals import post_auth_failed, post_auth_success
from authentication.utils import check_different_city_login_if_need
from jumpserver.utils import current_request
from users.models import User
from users.signals import post_user_change_password
from terminal.models import Session, Command
from .utils import write_login_log, create_operate_log
from . import models, serializers
from .models import OperateLog
from orgs.utils import current_org from orgs.utils import current_org
from perms.models import AssetPermission from perms.models import AssetPermission
from terminal.backends.command.serializers import SessionCommandSerializer from users.models import User
from users.signals import post_user_change_password
from assets.models import Asset
from jumpserver.utils import current_request
from authentication.signals import post_auth_failed, post_auth_success
from authentication.utils import check_different_city_login_if_need
from terminal.models import Session, Command
from terminal.serializers import SessionSerializer from terminal.serializers import SessionSerializer
from terminal.backends.command.serializers import SessionCommandSerializer
from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR
from common.utils import get_request_ip, get_logger, get_syslogger from common.utils import get_request_ip, get_logger, get_syslogger
from common.utils.encode import data_to_json from common.utils.encode import data_to_json
from . import models, serializers
from .const import ActionChoices
from .utils import write_login_log, create_operate_log
logger = get_logger(__name__) logger = get_logger(__name__)
sys_logger = get_syslogger(__name__) sys_logger = get_syslogger(__name__)
@ -97,9 +93,9 @@ M2M_NEED_RECORD = {
} }
M2M_ACTION_MAPER = { M2M_ACTION_MAPER = {
POST_ADD: OperateLog.ACTION_CREATE, POST_ADD: ActionChoices.create,
POST_REMOVE: OperateLog.ACTION_DELETE, POST_REMOVE: ActionChoices.delete,
POST_CLEAR: OperateLog.ACTION_DELETE, POST_CLEAR: ActionChoices.delete,
} }
@ -120,9 +116,9 @@ def on_m2m_changed(sender, action, instance, model, pk_set, **kwargs):
resource_type, resource_tmpl_add, resource_tmpl_remove = M2M_NEED_RECORD[sender_name] resource_type, resource_tmpl_add, resource_tmpl_remove = M2M_NEED_RECORD[sender_name]
action = M2M_ACTION_MAPER[action] action = M2M_ACTION_MAPER[action]
if action == OperateLog.ACTION_CREATE: if action == ActionChoices.create:
resource_tmpl = resource_tmpl_add resource_tmpl = resource_tmpl_add
elif action == OperateLog.ACTION_DELETE: elif action == ActionChoices.delete:
resource_tmpl = resource_tmpl_remove resource_tmpl = resource_tmpl_remove
else: else:
return return
@ -144,11 +140,11 @@ def on_m2m_changed(sender, action, instance, model, pk_set, **kwargs):
model_name: str(obj) model_name: str(obj)
})[:128] # `resource` 字段只有 128 个字符长 😔 })[:128] # `resource` 字段只有 128 个字符长 😔
to_create.append(OperateLog( to_create.append(models.OperateLog(
user=user, action=action, resource_type=resource_type, user=user, action=action, resource_type=resource_type,
resource=resource, remote_addr=remote_addr, org_id=org_id resource=resource, remote_addr=remote_addr, org_id=org_id
)) ))
OperateLog.objects.bulk_create(to_create) models.OperateLog.objects.bulk_create(to_create)
@receiver(post_save) @receiver(post_save)
@ -158,15 +154,15 @@ def on_object_created_or_update(sender, instance=None, created=False, update_fie
update_fields and 'last_login' in update_fields: update_fields and 'last_login' in update_fields:
return return
if created: if created:
action = models.OperateLog.ACTION_CREATE action = ActionChoices.create
else: else:
action = models.OperateLog.ACTION_UPDATE action = ActionChoices.update
create_operate_log(action, sender, instance) create_operate_log(action, sender, instance)
@receiver(pre_delete) @receiver(pre_delete)
def on_object_delete(sender, instance=None, **kwargs): def on_object_delete(sender, instance=None, **kwargs):
create_operate_log(models.OperateLog.ACTION_DELETE, sender, instance) create_operate_log(ActionChoices.delete, sender, instance)
@receiver(post_user_change_password, sender=User) @receiver(post_user_change_password, sender=User)

View File

@ -7,7 +7,6 @@ from django.utils.encoding import force_text
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
from common.utils import signer, crypto from common.utils import signer, crypto
__all__ = [ __all__ = [
'JsonMixin', 'JsonDictMixin', 'JsonListMixin', 'JsonTypeMixin', 'JsonMixin', 'JsonDictMixin', 'JsonListMixin', 'JsonTypeMixin',
'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField', 'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField',
@ -189,4 +188,3 @@ class PortField(models.IntegerField):
'validators': [MinValueValidator(0), MaxValueValidator(65535)] 'validators': [MinValueValidator(0), MaxValueValidator(65535)]
}) })
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

@ -21,6 +21,7 @@ __all__ = [
class ReadableHiddenField(serializers.HiddenField): class ReadableHiddenField(serializers.HiddenField):
""" 可读的 HiddenField """ """ 可读的 HiddenField """
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.write_only = False self.write_only = False

View File

@ -9,6 +9,7 @@ from rest_framework.request import Request
from common.exceptions import UserConfirmRequired from common.exceptions import UserConfirmRequired
from audits.utils import create_operate_log from audits.utils import create_operate_log
from audits.models import OperateLog from audits.models import OperateLog
from audits.const import ActionChoices
__all__ = ["PermissionsMixin", "RecordViewLogMixin", "UserConfirmRequiredExceptionMixin"] __all__ = ["PermissionsMixin", "RecordViewLogMixin", "UserConfirmRequiredExceptionMixin"]
@ -40,7 +41,7 @@ class PermissionsMixin(UserPassesTestMixin):
class RecordViewLogMixin: class RecordViewLogMixin:
ACTION = OperateLog.ACTION_VIEW ACTION = ActionChoices.view
@staticmethod @staticmethod
def get_resource_display(request): def get_resource_display(request):