Skip to content

Commit

Permalink
feat(backend): 提供自动分配iam权限给DBA的脚本 #6988
Browse files Browse the repository at this point in the history
  • Loading branch information
iSecloud committed Sep 20, 2024
1 parent f9efdce commit 1baad43
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 11 deletions.
1 change: 1 addition & 0 deletions dbm-ui/backend/env/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
BK_IAM_API_VERSION = get_type_env(key="BK_IAM_API_VERSION", _type=str, default="v1")
IAM_APP_URL = get_type_env(key="IAM_APP_URL", _type=str, default="https://iam.example.com")
BK_IAM_RESOURCE_API_HOST = get_type_env(key="BK_IAM_RESOURCE_API_HOST", _type=str, default="https://bkdbm.example.com")
BK_IAM_GRADE_MANAGER_ID = get_type_env(key="BK_IAM_GRADE_MANAGER_ID", _type=int, default=0)

# APIGW 相关配置
BK_APIGATEWAY_DOMAIN = get_type_env(key="BK_APIGATEWAY_DOMAIN", _type=str, default=BK_COMPONENT_API_URL)
Expand Down
101 changes: 91 additions & 10 deletions dbm-ui/backend/iam_app/dataclass/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,27 @@
specific language governing permissions and limitations under the License.
"""
import json
import logging
import os
import time
from collections import defaultdict
from typing import Any, Dict, List

from django.conf import settings
from django.utils.translation import ugettext as _

from ... import env
from ...db_meta.models import AppCache
from ...env import BK_IAM_SYSTEM_ID
from ..constans import CommonActionLabel
from ..exceptions import BaseIAMError
from ..handlers.client import IAM
from ..handlers.permission import Permission
from .actions import _all_actions
from .resources import ResourceEnum, ResourceMeta, _all_resources, _extra_instance_selections

logger = logging.getLogger("root")

IAM_SYSTEM_DEFINITION = {
"operation": "upsert_system",
"data": {
Expand Down Expand Up @@ -157,25 +166,22 @@ def generate_iam_migration_json(json_name: str = ""):
f.write(json.dumps(dbm_iam_json, ensure_ascii=False, indent=4))


def generate_iam_biz_maintain_json(label: str = CommonActionLabel.BIZ_MAINTAIN, json_name: str = ""):
"""
根据dataclass的定义自动生成业务运维的用户组迁移json
"""
def generate_resource_topo_auth(res_actions: list, bk_biz_id: int = None, bk_biz_name=None):
bk_biz_id = bk_biz_id or "{{biz_id}}"
bk_biz_name = bk_biz_name or "{{biz_name}}"

def get_resource_path_info(resource: ResourceMeta):
if ResourceEnum.BUSINESS not in [resource, resource.parent]:
paths = []
else:
paths = [[{"system": "bk_cmdb", "type": "biz", "id": "{{biz_id}}", "name": "{{biz_name}}"}]]
paths = [[{"system": "bk_cmdb", "type": "biz", "id": bk_biz_id, "name": bk_biz_name}]]
return {"system": resource.system_id, "type": resource.id, "paths": paths}

resources__actions_map: Dict[str, List[str]] = defaultdict(list)
biz_maintain_migrate_content: List[Dict[str, Any]] = []
resource_topo_auth_content: List[Dict[str, Any]] = []

# 聚合相同资源的动作
for action in _all_actions.values():
if label not in action.common_labels:
continue
for action in res_actions:
resource_ids = ",".join([resource.id for resource in action.related_resource_types])
resources__actions_map[resource_ids].append(action.id)

Expand All @@ -189,12 +195,87 @@ def get_resource_path_info(resource: ResourceMeta):
resource_infos = [get_resource_path_info(resource) for resource in resource_metas]
# 生成action的迁移信息
action_infos = [{"id": id} for id in action_ids]
biz_maintain_migrate_content.append(
resource_topo_auth_content.append(
{"system": BK_IAM_SYSTEM_ID, "actions": action_infos, "resources": resource_infos}
)

return resource_topo_auth_content


def generate_iam_biz_maintain_json(label: str = CommonActionLabel.BIZ_MAINTAIN, json_name: str = ""):
"""
根据dataclass的定义自动生成业务运维的用户组迁移json
"""
res_actions = [action for action in _all_actions.values() if label in action.common_labels]
biz_maintain_migrate_content = generate_resource_topo_auth(res_actions)

# 生成json文件
json_name = json_name or "biz_maintain_migrate.json"
migrate_json_path = os.path.join(settings.BASE_DIR, f"backend/iam_app/migration_json_files/{json_name}")
with open(migrate_json_path, "w+") as f:
f.write(json.dumps(biz_maintain_migrate_content, ensure_ascii=False, indent=4))


def assign_auth_to_group(iam: IAM, biz: AppCache, group_id):
"""
给单个用户组分配权限,这里的权限固定是DBA权限
"""
biz_actions = [action for action in _all_actions.values() if action.group not in [_("全局设置"), _("资源管理")]]
auth_contents = generate_resource_topo_auth(biz_actions, bk_biz_id=biz.bk_biz_id, bk_biz_name=biz.bk_biz_name)
for auth_info in auth_contents:
ok, message, data = iam._client.grant_user_group_actions(env.BK_IAM_SYSTEM_ID, group_id, data=auth_info)
if not ok:
raise BaseIAMError(_("用户组添加授权失败,错误信息: {}").format(message))


def assign_auth_to_dba(bk_biz_id: int, group_name: str, members: list):
"""
给DBA分配iam权限,具体是:
创建用户组 ---> 给用户组分配权限 ---> 成员加入用户组
"""
biz = AppCache.objects.get(bk_biz_id=bk_biz_id)
manager_id = env.BK_IAM_GRADE_MANAGER_ID
iam = Permission.get_iam_client()

# 创建用户组
group_data = {"groups": [{"name": group_name, "description": group_name}]}
ok, message, data = iam._client.create_user_groups(env.BK_IAM_SYSTEM_ID, manager_id, data=group_data)
if not ok:
raise BaseIAMError(_("创建用户组失败,错误信息: {}").format(message))

# 对用户组分配权限,动作不包含资源管理和全局设置
group_id = data[0]
assign_auth_to_group(iam, biz, data[0])

# 对用户组添加成员,默认过期时间是1年
expired_at = int(time.time() + 60 * 60 * 24 * 30 * 12)
members = [{"type": "user", "id": member} for member in members]
add_members_data = {"members": members, "expired_at": expired_at}
ok, message, data = iam._client.add_user_group_members(env.BK_IAM_SYSTEM_ID, group_id, data=add_members_data)
if not ok:
raise BaseIAMError(_("用户组添加成员{}失败,错误信息: {}").format(members, message))


def flush_groups_auth():
"""
刷新存量用户组权限
"""
iam = Permission.get_iam_client()
manager_id = env.BK_IAM_GRADE_MANAGER_ID

# 查询包含DBA名称的用户组,默认不超过500
params = {"name": "DBA", "page_size": 500, "page": 1}
ok, message, data = iam._client.query_user_groups(env.BK_IAM_SYSTEM_ID, manager_id, data=params)
if not ok:
raise BaseIAMError(_("用户组查询失败,错误信息: {}").format(message))

# 刷新权限
for group in data["results"]:
try:
# 提取用户组ID和业务
group_id, name = group["id"], group["name"]
biz = AppCache.objects.get(bk_biz_id=name.split("_")[0])
# 分配权限
assign_auth_to_group(iam, biz, group_id)
except Exception as e:
raise BaseIAMError(_("用户组{}刷新失败,错误信息:{}").format(group, e))
58 changes: 58 additions & 0 deletions dbm-ui/backend/iam_app/handlers/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at https://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""

from iam import IAM as BaseIAM
from iam.api.client import Client as IAMClient
from iam.api.http import http_get, http_post


class Client(IAMClient):
"""补充一些额外的api请求"""

# 创建用户组
def create_user_groups(self, system_id, grade_manager_id, data):
path = "/api/v2/open/management/systems/{system_id}/grade_managers/{grade_manager_id}/groups/".format(
system_id=system_id, grade_manager_id=grade_manager_id
)
ok, message, data = self._call_iam_api(http_post, path, data)
return ok, message, data

# 用户组授权
def grant_user_group_actions(self, system_id, group_id, data):
path = "/api/v2/open/management/systems/{system_id}/groups/{group_id}/policies".format(
system_id=system_id, group_id=group_id
)
ok, message, data = self._call_iam_api(http_post, path, data)
return ok, message, data

# 添加用户组成员
def add_user_group_members(self, system_id, group_id, data):
path = "/api/v2/open/management/systems/{system_id}/groups/{group_id}/members".format(
system_id=system_id, group_id=group_id
)
ok, message, data = self._call_iam_api(http_post, path, data)
return ok, message, data

# 查询用户组
def query_user_groups(self, system_id, grade_manager_id, data):
path = "/api/v2/open/management/systems/{system_id}/grade_managers/{grade_manager_id}/groups/".format(
system_id=system_id, grade_manager_id=grade_manager_id
)
ok, message, data = self._call_iam_api(http_get, path, data)
return ok, message, data


class IAM(BaseIAM):
def __init__(
self, app_code, app_secret, bk_iam_host=None, bk_paas_host=None, bk_apigateway_url=None, api_version="v2"
):
super().__init__(app_code, app_secret, bk_iam_host, bk_paas_host, bk_apigateway_url, api_version)
self._client = Client(app_code, app_secret, bk_iam_host, bk_paas_host, bk_apigateway_url)
3 changes: 2 additions & 1 deletion dbm-ui/backend/iam_app/handlers/permission.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from blueapps.account.models import User
from django.conf import settings
from django.utils.translation import ugettext as _
from iam import IAM, DummyIAM, MultiActionRequest, ObjectSet, Request, Resource, Subject, make_expression
from iam import DummyIAM, MultiActionRequest, ObjectSet, Request, Resource, Subject, make_expression
from iam.apply.models import (
ActionWithoutResources,
ActionWithResources,
Expand All @@ -36,6 +36,7 @@
from backend.iam_app.dataclass.actions import ActionEnum, ActionMeta, _all_actions
from backend.iam_app.dataclass.resources import ResourceEnum, ResourceMeta, _all_resources
from backend.iam_app.exceptions import ActionNotExistError, GetSystemInfoError, PermissionDeniedError
from backend.iam_app.handlers.client import IAM
from backend.utils.local import local

logger = logging.getLogger("root")
Expand Down
39 changes: 39 additions & 0 deletions dbm-ui/backend/iam_app/migration_json_files/initial.json
Original file line number Diff line number Diff line change
Expand Up @@ -8199,6 +8199,35 @@
"description_en": "REDIS_CLUSTER_MAXMEMORY_SET"
}
},
{
"operation": "upsert_action",
"data": {
"id": "redis_cluster_load_modules",
"name": "Redis 集群加载modules",
"name_en": "REDIS_CLUSTER_LOAD_MODULES",
"type": "execute",
"related_resource_types": [
{
"system_id": "bk_dbm",
"id": "redis",
"selection_mode": "all",
"related_instance_selections": [
{
"system_id": "bk_dbm",
"id": "redis_list"
}
]
}
],
"related_actions": [],
"version": 1,
"hidden": false,
"group": "Redis",
"subgroup": "工具箱",
"description": "Redis 集群加载modules",
"description_en": "REDIS_CLUSTER_LOAD_MODULES"
}
},
{
"operation": "upsert_action",
"data": {
Expand Down Expand Up @@ -10477,6 +10506,9 @@
},
{
"id": "redis_cluster_maxmemory_set"
},
{
"id": "redis_cluster_load_modules"
}
]
}
Expand Down Expand Up @@ -11705,6 +11737,9 @@
{
"id": "redis_cluster_maxmemory_set"
},
{
"id": "redis_cluster_load_modules"
},
{
"id": "kafka_scale_up"
},
Expand Down Expand Up @@ -12539,6 +12574,10 @@
{
"id": "redis_cluster_maxmemory_set",
"required": true
},
{
"id": "redis_cluster_load_modules",
"required": true
}
]
},
Expand Down
6 changes: 6 additions & 0 deletions dbm-ui/backend/iam_app/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,9 @@ class Meta:
class CheckAllowedResSerializer(serializers.Serializer):
class Meta:
swagger_schema_fields = {"example": mock_data.ACTION_CHECK_ALLOWED}


class AssignAuthToDBASerializer(serializers.Serializer):
bk_biz_id = serializers.IntegerField(help_text=_("业务ID"))
group_name = serializers.CharField(help_text=_("组名"))
members = serializers.ListField(help_text=_("成员列表"), child=serializers.CharField())
22 changes: 22 additions & 0 deletions dbm-ui/backend/iam_app/views/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@

from backend.bk_web import viewsets
from backend.bk_web.swagger import common_swagger_auto_schema
from backend.iam_app.dataclass import assign_auth_to_dba, flush_groups_auth
from backend.iam_app.handlers.permission import Permission
from backend.iam_app.serializers import (
AssignAuthToDBASerializer,
CheckAllowedResSerializer,
GetApplyDataResSerializer,
IamActionResourceRequestSerializer,
Expand Down Expand Up @@ -97,3 +99,23 @@ def simple_get_apply_data(self, request, *args, **kwargs):
resources = client.batch_make_resource_instance(self.validated_data["resources"])
apply_data, apply_url = client.get_apply_data([self.validated_data["action_id"]], [resources])
return Response({"permission": apply_data, "apply_url": apply_url})

@common_swagger_auto_schema(
operation_summary=_("自动分配权限给DBA"),
request_body=AssignAuthToDBASerializer(),
tags=[SWAGGER_TAG],
)
@action(detail=False, methods=["POST"], serializer_class=AssignAuthToDBASerializer)
def assign_auth_to_dba(self, request, *args, **kwargs):
data = self.validated_data
assign_auth_to_dba(bk_biz_id=data["bk_biz_id"], group_name=data["group_name"], members=data["members"])
return Response(_("权限分配成功!"))

@common_swagger_auto_schema(
operation_summary=_("存量用户组刷新权限"),
tags=[SWAGGER_TAG],
)
@action(detail=False, methods=["POST"])
def flush_groups_auth(self, request, *args, **kwargs):
flush_groups_auth()
return Response(_("存量用户组权限刷新成功!"))

0 comments on commit 1baad43

Please sign in to comment.