feat: Add permission check for reading account secrets based on system settings (#16337)

This commit is contained in:
fit2bot
2025-12-11 16:42:10 +08:00
committed by GitHub
parent af908480f4
commit 8e703d306c
11 changed files with 43 additions and 11 deletions

View File

@@ -1,3 +1,4 @@
from django.conf import settings
from django.db import transaction
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
@@ -166,7 +167,7 @@ class AccountViewSet(OrgBulkModelViewSet):
class AccountSecretsViewSet(AccountRecordViewLogMixin, AccountViewSet):
"""
因为可能要导出所有账号所以单独建立了一个 viewset
因为可能要导出所有账号,所以单独建立了一个 viewset
"""
serializer_classes = {
'default': serializers.AccountSecretSerializer,

View File

@@ -81,4 +81,7 @@ class IntegrationApplicationViewSet(OrgBulkModelViewSet):
remote_addr=get_request_ip(request), service=service.name, service_id=service.id,
account=f'{account.name}({account.username})', asset=f'{asset.name}({asset.address})',
)
return Response(data={'id': request.user.id, 'secret': account.secret})
# 根据配置决定是否返回密码
secret = account.secret if settings.SECURITY_ACCOUNT_SECRET_READ else None
return Response(data={'id': request.user.id, 'secret': secret})

View File

@@ -1,3 +1,5 @@
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from django_filters import rest_framework as drf_filters
from rest_framework import status
from rest_framework.decorators import action

View File

@@ -14,7 +14,7 @@ from accounts.models import Account, AccountTemplate, GatheredAccount
from accounts.tasks import push_accounts_to_assets_task
from assets.const import Category, AllTypes
from assets.models import Asset
from common.serializers import SecretReadableMixin, CommonBulkModelSerializer
from common.serializers import SecretReadableMixin, SecretReadableCheckMixin, CommonBulkModelSerializer
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
from common.utils import get_logger
from .base import BaseAccountSerializer, AuthValidateMixin
@@ -478,7 +478,7 @@ class AssetAccountBulkSerializer(
return results
class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
class AccountSecretSerializer(SecretReadableCheckMixin, SecretReadableMixin, AccountSerializer):
spec_info = serializers.DictField(label=_('Spec info'), read_only=True)
class Meta(AccountSerializer.Meta):
@@ -491,9 +491,10 @@ class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
exclude_backup_fields = [
'passphrase', 'push_now', 'params', 'spec_info'
]
secret_fields = ['secret']
class AccountHistorySerializer(serializers.ModelSerializer):
class AccountHistorySerializer(SecretReadableCheckMixin, serializers.ModelSerializer):
secret_type = LabeledChoiceField(choices=SecretType.choices, label=_('Secret type'))
secret = serializers.CharField(label=_('Secret'), read_only=True)
id = serializers.IntegerField(label=_('ID'), source='history_id', read_only=True)
@@ -509,6 +510,7 @@ class AccountHistorySerializer(serializers.ModelSerializer):
'history_user': {'label': _('User')},
'history_date': {'label': _('Date')},
}
secret_fields = ['secret']
class AccountTaskSerializer(serializers.Serializer):

View File

@@ -2,7 +2,7 @@ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.models import AccountTemplate
from common.serializers import SecretReadableMixin
from common.serializers import SecretReadableMixin, SecretReadableCheckMixin
from common.serializers.fields import ObjectRelatedField
from .base import BaseAccountSerializer
@@ -62,10 +62,11 @@ class AccountDetailTemplateSerializer(AccountTemplateSerializer):
fields = AccountTemplateSerializer.Meta.fields + ['spec_info']
class AccountTemplateSecretSerializer(SecretReadableMixin, AccountDetailTemplateSerializer):
class AccountTemplateSecretSerializer(SecretReadableCheckMixin, SecretReadableMixin, AccountDetailTemplateSerializer):
class Meta(AccountDetailTemplateSerializer.Meta):
fields = AccountDetailTemplateSerializer.Meta.fields
extra_kwargs = {
**AccountDetailTemplateSerializer.Meta.extra_kwargs,
'secret': {'write_only': False},
}
secret_fields = ['secret']

View File

@@ -30,12 +30,30 @@ __all__ = [
"CommonSerializerMixin",
"CommonBulkSerializerMixin",
"SecretReadableMixin",
"SecretReadableCheckMixin",
"CommonModelSerializer",
"CommonBulkModelSerializer",
"ResourceLabelsMixin",
]
class SecretReadableCheckMixin(serializers.Serializer):
"""
根据 SECURITY_ACCOUNT_SECRET_READ 配置控制密码字段的可读性
当配置为 False 时,密码字段返回 None
"""
def to_representation(self, instance):
ret = super().to_representation(instance)
if not settings.SECURITY_ACCOUNT_SECRET_READ:
secret_fields = getattr(self.Meta, 'secret_fields', ['secret'])
for field_name in secret_fields:
if field_name in ret:
ret[field_name] = '<REDACTED>'
return ret
class SecretReadableMixin(serializers.Serializer):
"""加密字段 (EncryptedField) 可读性"""

View File

@@ -1636,7 +1636,8 @@
"setVariable": "Set variable",
"userId": "User ID",
"userName": "User name",
"AccountSecretReadDisabled": "Account secret reading has been disabled by administrator",
"AccessToken": "Access tokens",
"AccessTokenTip": "Access Token is a temporary credential generated through the OAuth2 (Authorization Code Grant) flow using the JumpServer client, which is used to access protected resources.",
"Revoke": "Revoke"
}
}

View File

@@ -1648,5 +1648,6 @@
"selectFiles": "已选择选择{number}文件",
"AccessToken": "访问令牌",
"AccessTokenTip": "访问令牌是通过 JumpServer 客户端使用 OAuth2授权码授权流程生成的临时凭证用于访问受保护的资源。",
"Revoke": "撤销"
}
"Revoke": "撤销",
"AccountSecretReadDisabled": "账号密码读取功能已被管理员禁用"
}

View File

@@ -575,6 +575,7 @@ class Config(dict):
],
'SECURITY_SERVICE_ACCOUNT_REGISTRATION': 'auto',
'SECURITY_VIEW_AUTH_NEED_MFA': True,
'SECURITY_ACCOUNT_SECRET_READ': True,
'SECURITY_MAX_IDLE_TIME': 30,
'SECURITY_MAX_SESSION_TIME': 24,
'SECURITY_PASSWORD_EXPIRATION_TIME': 9999,

View File

@@ -60,6 +60,7 @@ VERIFY_CODE_TTL = CONFIG.VERIFY_CODE_TTL
SECURITY_MFA_VERIFY_TTL = CONFIG.SECURITY_MFA_VERIFY_TTL
SECURITY_UNCOMMON_USERS_TTL = CONFIG.SECURITY_UNCOMMON_USERS_TTL
SECURITY_VIEW_AUTH_NEED_MFA = CONFIG.SECURITY_VIEW_AUTH_NEED_MFA
SECURITY_ACCOUNT_SECRET_READ = CONFIG.SECURITY_ACCOUNT_SECRET_READ
SECURITY_SERVICE_ACCOUNT_REGISTRATION = CONFIG.SECURITY_SERVICE_ACCOUNT_REGISTRATION
SECURITY_LOGIN_CAPTCHA_ENABLED = CONFIG.SECURITY_LOGIN_CAPTCHA_ENABLED
SECURITY_MFA_IN_LOGIN_PAGE = CONFIG.SECURITY_MFA_IN_LOGIN_PAGE
@@ -268,4 +269,4 @@ LOKI_BASE_URL = CONFIG.LOKI_BASE_URL
TOOL_USER_ENABLED = CONFIG.TOOL_USER_ENABLED
SUGGESTION_LIMIT = CONFIG.SUGGESTION_LIMIT
MCP_ENABLED = CONFIG.MCP_ENABLED
MCP_ENABLED = CONFIG.MCP_ENABLED

View File

@@ -21,6 +21,7 @@ class PrivateSettingSerializer(PublicSettingSerializer):
TICKET_AUTHORIZE_DEFAULT_TIME_UNIT = serializers.CharField()
AUTH_LDAP_SYNC_ORG_IDS = serializers.ListField()
SECURITY_MAX_IDLE_TIME = serializers.IntegerField()
SECURITY_ACCOUNT_SECRET_READ = serializers.BooleanField()
SECURITY_VIEW_AUTH_NEED_MFA = serializers.BooleanField()
SECURITY_MFA_AUTH = serializers.IntegerField()
SECURITY_MFA_VERIFY_TTL = serializers.IntegerField()