Skip to content

Commit

Permalink
feat(backend): 权限修改支持会签模式 TencentBlueKing#6751
Browse files Browse the repository at this point in the history
  • Loading branch information
iSecloud authored and zhangzhw8 committed Nov 19, 2024
1 parent 07d7a91 commit b348724
Show file tree
Hide file tree
Showing 37 changed files with 1,195 additions and 411 deletions.
2 changes: 2 additions & 0 deletions dbm-ui/backend/configuration/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class SystemSettingsEnum(str, StructuredEnum):
BKM_DBM_TOKEN = EnumField("BKM_DBM_TOKEN", _("监控数据源token"))
BKM_DBM_REPORT = EnumField("BKM_DBM_REPORT", _("mysql/redis-监控自定义上报: dataid/token"))
FREE_BK_MODULE_ID = EnumField("FREE_BK_MODULE_ID", _("业务空闲模块ID"))
VIRTUAL_USERS = EnumField("VIRTUAL_USERS", _("平台调用的虚拟账号列表"))
# 主机默认统一转移到 DBM 业务下托管,若业务 ID 属于这个列表,则转移到对应的业务下
INDEPENDENT_HOSTING_BIZS = EnumField("INDEPENDENT_HOSTING_BIZS", _("独立托管机器的业务列表"))
BF_WHITELIST_BIZS = EnumField("BF_WHITELIST_BIZS", _("BF业务白名单"))
Expand Down Expand Up @@ -213,6 +214,7 @@ class BizSettingsEnum(str, StructuredEnum):
[SystemSettingsEnum.AFFINITY, "list", [], _("环境的容灾要求")],
[SystemSettingsEnum.SYSTEM_MSG_TYPE, "list", ["weixin", "mail"], _("系统消息通知方式")],
[SystemSettingsEnum.PADDING_PROXY_CLUSTER_LIST, "list", [], _("补全proxy的集群域名列表")],
[SystemSettingsEnum.VIRTUAL_USERS, "list", [], _("平台调用的虚拟账户列表")],
]

# 环境配置项 是否支持DNS解析 pulsar flow used
Expand Down
18 changes: 17 additions & 1 deletion dbm-ui/backend/db_services/dbpermission/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ class FormatType(str, StructuredEnum):
CLUSTER = EnumField("cluster", _("cluster"))


class RuleActionType(str, StructuredEnum):
"""权限操作类型"""

AUTH = EnumField("auth", _("授权"))
CREATE = EnumField("create", _("创建"))
CHANGE = EnumField("change", _("修改"))
DELETE = EnumField("delete", _("删除"))


class AuthorizeExcelHeader(str, StructuredEnum):
"""授权excel的头部信息"""

Expand All @@ -117,6 +126,14 @@ class AuthorizeExcelHeader(str, StructuredEnum):
ERROR = EnumField("错误信息/提示信息", _("错误信息/提示信息"))


# 授权字段和授权excel头的映射
AUTHORIZE_KEY__EXCEL_FIELD_MAP = {
"user": AuthorizeExcelHeader.USER,
"access_dbs": AuthorizeExcelHeader.ACCESS_DBS,
"source_ips": AuthorizeExcelHeader.SOURCE_IPS,
"target_instances": AuthorizeExcelHeader.TARGET_INSTANCES,
}

# 授权数据过期时间
AUTHORIZE_DATA_EXPIRE_TIME = 60 * 60 * 6

Expand All @@ -126,5 +143,4 @@ class AuthorizeExcelHeader(str, StructuredEnum):
# 账号名称最大长度
MAX_ACCOUNT_LENGTH = 31


DPRIV_PARAMETER_MAP = {"account_type": "cluster_type", "rule_ids": "ids", "privilege": "privs", "access_db": "dbname"}
42 changes: 32 additions & 10 deletions dbm-ui/backend/db_services/dbpermission/db_account/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,19 @@
from backend.components.mysql_priv_manager.client import DBPrivManagerApi
from backend.core.encrypt.constants import AsymmetricCipherConfigType
from backend.core.encrypt.handlers import AsymmetricHandler
from backend.db_services.dbpermission.constants import DPRIV_PARAMETER_MAP, AccountType
from backend.db_services.dbpermission.constants import DPRIV_PARAMETER_MAP, AccountType, RuleActionType
from backend.db_services.dbpermission.db_account.dataclass import (
AccountMeta,
AccountPrivMeta,
AccountRuleMeta,
AccountUserMeta,
)
from backend.db_services.dbpermission.db_account.signals import create_account_signal
from backend.db_services.dbpermission.db_authorize.models import DBRuleActionLog
from backend.db_services.mysql.open_area.models import TendbOpenAreaConfig
from backend.db_services.mysql.permission.exceptions import DBPermissionBaseException
from backend.ticket.constants import TicketStatus, TicketType
from backend.ticket.models import Ticket
from backend.utils.excel import ExcelHandler

logger = logging.getLogger("root")
Expand Down Expand Up @@ -63,6 +66,17 @@ def _decrypt_password(password: str) -> str:

def _format_account_rules(self, account_rules_list: Dict) -> Dict:
"""格式化账号权限列表信息"""
# 查询规则变更的单据,补充规则正在运行的状态
try:
ticket_type = getattr(TicketType, f"{self.account_type.upper()}_ACCOUNT_RULE_CHANGE")
priv_tickets = Ticket.objects.filter(status=TicketStatus.RUNNING, ticket_type=ticket_type)
rule__action_map = {}
for t in priv_tickets:
rule__action_map[t.details["rule_id"]] = {"action": t.details["action"], "ticket_id": t.id}
except (AttributeError, Exception):
rule__action_map = {}

# 格式化账号规则的字段信息
for account_rules in account_rules_list["items"]:
account_rules["account"]["account_id"] = account_rules["account"].pop("id")

Expand All @@ -74,6 +88,7 @@ def _format_account_rules(self, account_rules_list: Dict) -> Dict:
rule["rule_id"] = rule.pop("id")
rule["access_db"] = rule.pop("dbname")
rule["privilege"] = rule.pop("priv")
rule["priv_ticket"] = rule__action_map.get(rule["rule_id"], {})

return account_rules_list

Expand Down Expand Up @@ -147,6 +162,7 @@ def add_account_rule(self, account_rule: AccountRuleMeta) -> Optional[Any]:
"dbname": account_rule.access_db,
}
)
# DBRuleActionLog.create_log(account_rule, self.operator, action=RuleActionType.CHANGE)
return resp

def query_account_rules(self, account_rule: AccountRuleMeta):
Expand Down Expand Up @@ -211,7 +227,6 @@ def modify_account_rule(self, account_rule: AccountRuleMeta) -> Optional[Any]:
- 修改账号规则
:param account_rule: 账号规则元信息
"""

resp = DBPrivManagerApi.modify_account_rule(
{
"bk_biz_id": self.bk_biz_id,
Expand All @@ -223,6 +238,9 @@ def modify_account_rule(self, account_rule: AccountRuleMeta) -> Optional[Any]:
"priv": account_rule.privilege,
}
)
DBRuleActionLog.create_log(
account_rule.account_id, account_rule.rule_id, self.operator, action=RuleActionType.CHANGE
)
return resp

def delete_account_rule(self, account_rule: AccountRuleMeta) -> Optional[Any]:
Expand All @@ -233,7 +251,7 @@ def delete_account_rule(self, account_rule: AccountRuleMeta) -> Optional[Any]:
# 如果账号规则与其他地方耦合,需要进行判断
config = TendbOpenAreaConfig.objects.filter(related_authorize__contains=[account_rule.rule_id])
if config.exists():
raise DBPermissionBaseException(_("当前授权规则已被开区模板{}引用,不允许删除").format(config.first().name))
raise DBPermissionBaseException(_("当前规则已被开区模板{}引用,不允许删除").format(config.first().config_name))

resp = DBPrivManagerApi.delete_account_rule(
{
Expand All @@ -243,18 +261,22 @@ def delete_account_rule(self, account_rule: AccountRuleMeta) -> Optional[Any]:
"id": [account_rule.rule_id],
}
)
DBRuleActionLog.create_log(
account_rule.account_id, account_rule.rule_id, self.operator, action=RuleActionType.DELETE
)
return resp

@classmethod
def aggregate_user_db_privileges(cls, bk_biz_id: int, account_type: AccountType) -> Dict[str, Dict[str, List]]:
account_rules = DBPrivManagerApi.list_account_rules({"bk_biz_id": bk_biz_id, "cluster_type": account_type})[
"items"
]
# 按照user,accessdb进行聚合
def aggregate_user_db_rules(
cls, bk_biz_id: int, account_type: AccountType, rule_key: str = "priv"
) -> Dict[str, Dict[str, Any]]:
"""获得user和db对应的规则对象"""
account_rules = DBPrivManagerApi.list_account_rules({"bk_biz_id": bk_biz_id, "cluster_type": account_type})
# 按照user,accessdb进行聚合规则
user_db__rules = defaultdict(dict)
for account_rule in account_rules:
for account_rule in account_rules["items"]:
account, rules = account_rule["account"], account_rule["rules"]
user_db__rules[account["user"]] = {rule["dbname"]: rule["priv"] for rule in rules}
user_db__rules[account["user"]] = {rule["dbname"]: rule[rule_key] if rule_key else rule for rule in rules}
return user_db__rules

def paginate_match_ips(self, data, privs_format, offset, limit):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,24 +190,21 @@ class RuleTypeSerializer(serializers.Serializer):
)

account_id = serializers.IntegerField(help_text=_("账号ID"))
access_db = serializers.CharField(help_text=_("访问DB"))
privilege = RuleTypeSerializer(help_text=_("授权规则"))
access_db = serializers.CharField(help_text=_("访问DB"), required=False)
privilege = RuleTypeSerializer(help_text=_("授权规则"), required=False)
account_type = serializers.ChoiceField(help_text=_("账号类型"), choices=AccountType.get_choices())

class Meta:
swagger_schema_fields = {"example": mock_data.ADD_MYSQL_ACCOUNT_RULE_REQUEST}


class ModifyMySQLAccountRuleSerializer(AddAccountRuleSerializer):
class ModifyAccountRuleSerializer(AddAccountRuleSerializer):
rule_id = serializers.IntegerField(help_text=_("规则ID"))

class Meta:
swagger_schema_fields = {"example": mock_data.MODIFY_MYSQL_ACCOUNT_RULE_REQUEST}


class DeleteAccountRuleSerializer(serializers.Serializer):
rule_id = serializers.IntegerField(help_text=_("规则ID"))
account_type = serializers.ChoiceField(help_text=_("账号类型"), choices=AccountType.get_choices())

class DeleteAccountRuleSerializer(ModifyAccountRuleSerializer):
class Meta:
swagger_schema_fields = {"example": mock_data.DELETE_MYSQL_ACCOUNT_RULE_REQUEST}
6 changes: 3 additions & 3 deletions dbm-ui/backend/db_services/dbpermission/db_account/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
FilterAccountRulesSerializer,
FilterPrivSerializer,
ListAccountRulesSerializer,
ModifyMySQLAccountRuleSerializer,
ModifyAccountRuleSerializer,
PageAccountRulesSerializer,
QueryAccountRulesSerializer,
UpdateAccountPasswordSerializer,
Expand Down Expand Up @@ -177,9 +177,9 @@ def query_account_rules(self, request, bk_biz_id):
)

@common_swagger_auto_schema(
operation_summary=_("修改账号规则"), request_body=ModifyMySQLAccountRuleSerializer(), tags=[SWAGGER_TAG]
operation_summary=_("修改账号规则"), request_body=ModifyAccountRuleSerializer(), tags=[SWAGGER_TAG]
)
@action(methods=["POST"], detail=False, serializer_class=ModifyMySQLAccountRuleSerializer)
@action(methods=["POST"], detail=False, serializer_class=ModifyAccountRuleSerializer)
def modify_account_rule(self, request, bk_biz_id):
return self._view_common_handler(
request=request,
Expand Down
49 changes: 44 additions & 5 deletions dbm-ui/backend/db_services/dbpermission/db_authorize/dataclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@
from backend.constants import IP_RE_PATTERN
from backend.db_meta.enums import ClusterEntryType
from backend.db_meta.models import ClusterEntry
from backend.db_services.dbpermission.db_authorize.models import AuthorizeRecord
from backend.db_services.dbpermission.constants import (
AUTHORIZE_KEY__EXCEL_FIELD_MAP,
EXCEL_DIVIDER,
AuthorizeExcelHeader,
)
from backend.flow.plugins.components.collections.common.base_service import BaseService
from backend.ticket.constants import FlowType
from backend.ticket.models import Flow


@dataclass
Expand Down Expand Up @@ -48,12 +55,35 @@ def from_dict(cls, init_data: Dict) -> "AuthorizeMeta":
@classmethod
def from_excel_data(cls, excel_data: Dict, cluster_type: str) -> "AuthorizeMeta":
"""从权限excel数据解析为AuthorizeMeta, 每个集群类型自己实现"""
raise NotImplementedError
return cls(
user=excel_data[AuthorizeExcelHeader.USER],
access_dbs=excel_data[AuthorizeExcelHeader.ACCESS_DBS].split(EXCEL_DIVIDER),
target_instances=excel_data[AuthorizeExcelHeader.TARGET_INSTANCES].split(EXCEL_DIVIDER),
source_ips=ExcelAuthorizeMeta.format_ip(excel_data.get(AuthorizeExcelHeader.SOURCE_IPS)),
cluster_type=cluster_type,
)

@classmethod
def serializer_record_data(cls, record_queryset: List[AuthorizeRecord]) -> List[Dict]:
def serializer_record_data(cls, ticket_id: int) -> List[Dict]:
"""将授权记录进行序列化"""
raise NotImplementedError

def __format(_data):
if isinstance(_data, (list, dict)):
return "\n".join(_data)
return _data

# 目前授权只有:授权单据和开区单据,仅一个inner flow,可以直接取first
flow = Flow.objects.filter(ticket_id=ticket_id, flow_type=FlowType.INNER_FLOW).first()
authorize_results = BaseService.get_flow_output(flow)["data"].get("authorize_results", [])
field_map = AUTHORIZE_KEY__EXCEL_FIELD_MAP

record_data_list = []
for index, info in enumerate(flow.details["ticket_data"]["rules_set"]):
data = {field_map[field]: __format(value) for field, value in info.items() if field in field_map}
data.update({AuthorizeExcelHeader.ERROR: authorize_results[index]})
record_data_list.append(data)

return record_data_list


@dataclass
Expand All @@ -73,11 +103,20 @@ def from_dict(cls, init_data: Dict) -> "ExcelAuthorizeMeta":

@classmethod
def format_ip(cls, ips: str):
if not ips:
return []
# 编译捕获ip:port的正则表达式(注意用?:取消分组)
ip_pattern = re.compile(IP_RE_PATTERN)
return ip_pattern.findall(ips)

@classmethod
def serialize_excel_data(cls, data: Dict) -> Dict:
"""将数据解析为权限excel data类的数据, 每个集群类型自己实现"""
raise NotImplementedError
excel_data = {
AuthorizeExcelHeader.USER: data["user"],
AuthorizeExcelHeader.TARGET_INSTANCES: EXCEL_DIVIDER.join(data["target_instances"]),
AuthorizeExcelHeader.ACCESS_DBS: EXCEL_DIVIDER.join([rule["dbname"] for rule in data["account_rules"]]),
AuthorizeExcelHeader.SOURCE_IPS: EXCEL_DIVIDER.join(cls.format_ip(str(data.get("source_ips")))),
AuthorizeExcelHeader.ERROR: data["message"],
}
return excel_data
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from backend.db_meta.enums import ClusterType
from backend.db_services.dbpermission.constants import AUTHORIZE_DATA_EXPIRE_TIME, AccountType
from backend.db_services.dbpermission.db_authorize.dataclass import AuthorizeMeta, ExcelAuthorizeMeta
from backend.db_services.dbpermission.db_authorize.models import AuthorizeRecord
from backend.utils.cache import data_cache
from backend.utils.excel import ExcelHandler

Expand Down Expand Up @@ -195,8 +194,7 @@ def get_authorize_info_excel(self, excel_authorize: ExcelAuthorizeMeta) -> HttpR
# 单据走来的excel下载
if excel_authorize.ticket_id:
excel_name = "authorize_results.xlsx"
record_queryset = AuthorizeRecord.get_authorize_records_by_ticket(excel_authorize.ticket_id)
excel_data_dict__list = self.authorize_meta.serializer_record_data(record_queryset)
excel_data_dict__list = self.authorize_meta.serializer_record_data(excel_authorize.ticket_id)

# 授权ID走来的excel下载
if excel_authorize.authorize_uid:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 3.2.25 on 2024-09-02 03:06

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("db_authorize", "0001_initial"),
]

operations = [
migrations.CreateModel(
name="DBRuleActionLog",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("operator", models.CharField(max_length=32, verbose_name="操作人")),
("record_time", models.DateTimeField(auto_now=True, verbose_name="记录时间")),
("account_id", models.IntegerField(verbose_name="账号ID")),
("rule_id", models.IntegerField(verbose_name="权限规则ID")),
(
"action_type",
models.CharField(
choices=[("auth", "授权"), ("create", "创建"), ("change", "修改"), ("delete", "删除")],
max_length=32,
verbose_name="操作类型",
),
),
],
options={
"verbose_name": "权限变更记录",
"verbose_name_plural": "权限变更记录",
"ordering": ["-record_time"],
},
),
migrations.AddIndex(
model_name="dbruleactionlog",
index=models.Index(fields=["rule_id"], name="db_authoriz_rule_id_35a12d_idx"),
),
migrations.AddIndex(
model_name="dbruleactionlog",
index=models.Index(fields=["account_id"], name="db_authoriz_account_06c480_idx"),
),
]
Loading

0 comments on commit b348724

Please sign in to comment.