mirror of https://github.com/jumpserver/jumpserver
feat: data masking
parent
7cd1e4d3a0
commit
b16304c48a
|
|
@ -3,3 +3,4 @@ from .connect_method import *
|
||||||
from .login_acl import *
|
from .login_acl import *
|
||||||
from .login_asset_acl import *
|
from .login_asset_acl import *
|
||||||
from .login_asset_check import *
|
from .login_asset_check import *
|
||||||
|
from .data_masking import *
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
from common.api import JMSBulkModelViewSet
|
||||||
|
|
||||||
|
from orgs.utils import tmp_to_root_org
|
||||||
|
from .common import ACLUserFilterMixin
|
||||||
|
from ..models import DataMaskingRule
|
||||||
|
from .. import serializers
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['DataMaskingRuleViewSet']
|
||||||
|
|
||||||
|
|
||||||
|
class DataMaskingRuleFilter(ACLUserFilterMixin):
|
||||||
|
class Meta:
|
||||||
|
model = DataMaskingRule
|
||||||
|
fields = ('name', 'action')
|
||||||
|
|
||||||
|
|
||||||
|
class DataMaskingRuleViewSet(JMSBulkModelViewSet):
|
||||||
|
queryset = DataMaskingRule.objects.all()
|
||||||
|
filterset_class = DataMaskingRuleFilter
|
||||||
|
search_fields = ('name',)
|
||||||
|
serializer_class = serializers.DataMaskingRuleSerializer
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
with tmp_to_root_org():
|
||||||
|
return super().filter_queryset(queryset)
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Generated by Django 4.1.13 on 2025-10-07 16:16
|
||||||
|
|
||||||
|
import common.db.fields
|
||||||
|
from django.conf import settings
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('acls', '0002_auto_20210926_1047'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DataMaskingRule',
|
||||||
|
fields=[
|
||||||
|
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
|
||||||
|
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
|
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')),
|
||||||
|
('action', models.CharField(default='reject', max_length=64, verbose_name='Action')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Active')),
|
||||||
|
('users', common.db.fields.JSONManyToManyField(default=dict, to='users.User', verbose_name='Users')),
|
||||||
|
('assets', common.db.fields.JSONManyToManyField(default=dict, to='assets.Asset', verbose_name='Assets')),
|
||||||
|
('accounts', models.JSONField(default=list, verbose_name='Accounts')),
|
||||||
|
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||||
|
('fields_pattern', models.CharField(default='password', max_length=128, verbose_name='Fields pattern')),
|
||||||
|
('masking_method', models.CharField(choices=[('fixed_char', 'Fixed Character Replacement'), ('hide_middle', 'Hide Middle Characters'), ('keep_prefix', 'Keep Prefix Only'), ('keep_suffix', 'Keep Suffix Only')], default='fixed_char', max_length=32, verbose_name='Masking Method')),
|
||||||
|
('mask_pattern', models.CharField(blank=True, default='######', max_length=128, null=True, verbose_name='Mask Pattern')),
|
||||||
|
('reviewers', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Data Masking Rule',
|
||||||
|
'unique_together': {('org_id', 'name')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -2,3 +2,4 @@ from .command_acl import *
|
||||||
from .connect_method import *
|
from .connect_method import *
|
||||||
from .login_acl import *
|
from .login_acl import *
|
||||||
from .login_asset_acl import *
|
from .login_asset_acl import *
|
||||||
|
from .data_masking import *
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from acls.models import UserAssetAccountBaseACL
|
||||||
|
from common.utils import get_logger
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
__all__ = ['MaskingMethod', 'DataMaskingRule']
|
||||||
|
|
||||||
|
|
||||||
|
class MaskingMethod(models.TextChoices):
|
||||||
|
fixed_char = "fixed_char", _("Fixed Character Replacement") # 固定字符替换
|
||||||
|
hide_middle = "hide_middle", _("Hide Middle Characters") # 隐藏中间几位
|
||||||
|
keep_prefix = "keep_prefix", _("Keep Prefix Only") # 只保留前缀
|
||||||
|
keep_suffix = "keep_suffix", _("Keep Suffix Only") # 只保留后缀
|
||||||
|
|
||||||
|
|
||||||
|
class DataMaskingRule(UserAssetAccountBaseACL):
|
||||||
|
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||||
|
fields_pattern = models.CharField(max_length=128, default='password', verbose_name=_("Fields pattern"))
|
||||||
|
|
||||||
|
masking_method = models.CharField(
|
||||||
|
max_length=32,
|
||||||
|
choices=MaskingMethod.choices,
|
||||||
|
default=MaskingMethod.fixed_char,
|
||||||
|
verbose_name=_("Masking Method"),
|
||||||
|
)
|
||||||
|
mask_pattern = models.CharField(
|
||||||
|
max_length=128,
|
||||||
|
verbose_name=_("Mask Pattern"),
|
||||||
|
default="######",
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = [('org_id', 'name')]
|
||||||
|
verbose_name = _("Data Masking Rule")
|
||||||
|
|
@ -3,3 +3,4 @@ from .connect_method import *
|
||||||
from .login_acl import *
|
from .login_acl import *
|
||||||
from .login_asset_acl import *
|
from .login_asset_acl import *
|
||||||
from .login_asset_check import *
|
from .login_asset_check import *
|
||||||
|
from .data_masking import *
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
from common.serializers.fields import LabeledChoiceField
|
||||||
|
from .base import BaseUserAssetAccountACLSerializer as BaseSerializer
|
||||||
|
from common.serializers.mixin import CommonBulkModelSerializer
|
||||||
|
from ..models import DataMaskingRule
|
||||||
|
|
||||||
|
__all__ = ['DataMaskingRuleSerializer']
|
||||||
|
|
||||||
|
from ..models.data_masking import MaskingMethod
|
||||||
|
|
||||||
|
|
||||||
|
class DataMaskingRuleSerializer(BaseSerializer, CommonBulkModelSerializer):
|
||||||
|
masking_method = LabeledChoiceField(
|
||||||
|
choices=MaskingMethod, default=MaskingMethod.fixed_char, label='Masking Method'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(BaseSerializer.Meta):
|
||||||
|
model = DataMaskingRule
|
||||||
|
fields = BaseSerializer.Meta.fields + ['fields_pattern', 'masking_method', 'mask_pattern']
|
||||||
|
|
@ -11,6 +11,7 @@ router.register(r'login-asset-acls', api.LoginAssetACLViewSet, 'login-asset-acl'
|
||||||
router.register(r'command-filter-acls', api.CommandFilterACLViewSet, 'command-filter-acl')
|
router.register(r'command-filter-acls', api.CommandFilterACLViewSet, 'command-filter-acl')
|
||||||
router.register(r'command-groups', api.CommandGroupViewSet, 'command-group')
|
router.register(r'command-groups', api.CommandGroupViewSet, 'command-group')
|
||||||
router.register(r'connect-method-acls', api.ConnectMethodACLViewSet, 'connect-method-acl')
|
router.register(r'connect-method-acls', api.ConnectMethodACLViewSet, 'connect-method-acl')
|
||||||
|
router.register(r'data-masking-rules', api.DataMaskingRuleViewSet, 'data-masking-rule')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('login-asset/check/', api.LoginAssetCheckAPI.as_view(), name='login-asset-check'),
|
path('login-asset/check/', api.LoginAssetCheckAPI.as_view(), name='login-asset-check'),
|
||||||
|
|
|
||||||
|
|
@ -338,6 +338,18 @@ class ConnectionToken(JMSOrgBaseModel):
|
||||||
acls = CommandFilterACL.filter_queryset(**kwargs).valid()
|
acls = CommandFilterACL.filter_queryset(**kwargs).valid()
|
||||||
return acls
|
return acls
|
||||||
|
|
||||||
|
@lazyproperty
|
||||||
|
def data_masking_rules(self):
|
||||||
|
from acls.models import DataMaskingRule
|
||||||
|
kwargs = {
|
||||||
|
'user': self.user,
|
||||||
|
'asset': self.asset,
|
||||||
|
'account': self.account_object,
|
||||||
|
}
|
||||||
|
with tmp_to_org(self.asset.org_id):
|
||||||
|
rules = DataMaskingRule.filter_queryset(**kwargs).valid()
|
||||||
|
return rules
|
||||||
|
|
||||||
|
|
||||||
class SuperConnectionToken(ConnectionToken):
|
class SuperConnectionToken(ConnectionToken):
|
||||||
_type = ConnectionTokenType.SUPER
|
_type = ConnectionTokenType.SUPER
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from accounts.const import SecretType
|
from accounts.const import SecretType
|
||||||
from accounts.models import Account
|
from accounts.models import Account
|
||||||
from acls.models import CommandGroup, CommandFilterACL
|
from acls.models import CommandGroup, CommandFilterACL, DataMaskingRule
|
||||||
from assets.models import Asset, Platform, Gateway, Zone
|
from assets.models import Asset, Platform, Gateway, Zone
|
||||||
from assets.serializers.asset import AssetProtocolsSerializer
|
from assets.serializers.asset import AssetProtocolsSerializer
|
||||||
from assets.serializers.platform import PlatformSerializer
|
from assets.serializers.platform import PlatformSerializer
|
||||||
|
|
@ -83,6 +83,14 @@ class _ConnectionTokenGatewaySerializer(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class _ConnectionTokenDataMaskingRuleSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = DataMaskingRule
|
||||||
|
fields = ['id', 'name', 'fields_pattern',
|
||||||
|
'masking_method', 'mask_pattern',
|
||||||
|
'is_active', 'priority']
|
||||||
|
|
||||||
|
|
||||||
class _ConnectionTokenCommandFilterACLSerializer(serializers.ModelSerializer):
|
class _ConnectionTokenCommandFilterACLSerializer(serializers.ModelSerializer):
|
||||||
command_groups = ObjectRelatedField(
|
command_groups = ObjectRelatedField(
|
||||||
many=True, required=False, queryset=CommandGroup.objects,
|
many=True, required=False, queryset=CommandGroup.objects,
|
||||||
|
|
@ -139,6 +147,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin):
|
||||||
platform = _ConnectionTokenPlatformSerializer(read_only=True)
|
platform = _ConnectionTokenPlatformSerializer(read_only=True)
|
||||||
zone = ObjectRelatedField(queryset=Zone.objects, required=False, label=_('Domain'))
|
zone = ObjectRelatedField(queryset=Zone.objects, required=False, label=_('Domain'))
|
||||||
command_filter_acls = _ConnectionTokenCommandFilterACLSerializer(read_only=True, many=True)
|
command_filter_acls = _ConnectionTokenCommandFilterACLSerializer(read_only=True, many=True)
|
||||||
|
data_masking_rules = _ConnectionTokenDataMaskingRuleSerializer(read_only=True, many=True)
|
||||||
expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True)
|
expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True)
|
||||||
connect_method = _ConnectTokenConnectMethodSerializer(read_only=True, source='connect_method_object')
|
connect_method = _ConnectTokenConnectMethodSerializer(read_only=True, source='connect_method_object')
|
||||||
connect_options = serializers.JSONField(read_only=True)
|
connect_options = serializers.JSONField(read_only=True)
|
||||||
|
|
@ -149,7 +158,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin):
|
||||||
model = ConnectionToken
|
model = ConnectionToken
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'value', 'user', 'asset', 'account',
|
'id', 'value', 'user', 'asset', 'account',
|
||||||
'platform', 'command_filter_acls', 'protocol',
|
'platform', 'command_filter_acls', 'data_masking_rules', 'protocol',
|
||||||
'zone', 'gateway', 'actions', 'expire_at',
|
'zone', 'gateway', 'actions', 'expire_at',
|
||||||
'from_ticket', 'expire_now', 'connect_method',
|
'from_ticket', 'expire_now', 'connect_method',
|
||||||
'connect_options', 'face_monitor_token'
|
'connect_options', 'face_monitor_token'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue