Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(backend): 提供自动分配iam权限给DBA的脚本 #6988 #6989

Merged
merged 1 commit into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dbm-ui/backend/env/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,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
115 changes: 100 additions & 15 deletions dbm-ui/backend/iam_app/dataclass/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,28 @@
specific language governing permissions and limitations under the License.
"""
import json
import logging
import os
import re
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 ...env import BK_IAM_SYSTEM_ID
from backend import env
from backend.db_meta.models import AppCache

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 @@ -48,7 +58,7 @@ def generate_iam_migration_json(json_name: str = ""):

# 获取资源的json内容
for resource in _all_resources.values():
if resource.system_id != BK_IAM_SYSTEM_ID:
if resource.system_id != env.BK_IAM_SYSTEM_ID:
continue

iam_resources.append(resource.to_json())
Expand Down Expand Up @@ -90,7 +100,7 @@ def generate_iam_migration_json(json_name: str = ""):
continue
related_resource = action.related_resource_types[0]
# 不关联跨系统和特殊资源(dbtype)
if related_resource.system_id != BK_IAM_SYSTEM_ID:
if related_resource.system_id != env.BK_IAM_SYSTEM_ID:
continue
if related_resource in [ResourceEnum.DBTYPE]:
continue
Expand Down Expand Up @@ -149,33 +159,30 @@ def generate_iam_migration_json(json_name: str = ""):
iam_json_content.append({"operation": "upsert_resource_creator_actions", "data": iam_resource_creator_actions})

# 获取dbm在iam完整的注册json
dbm_iam_json = {"system_id": BK_IAM_SYSTEM_ID, "operations": iam_json_content}
dbm_iam_json = {"system_id": env.BK_IAM_SYSTEM_ID, "operations": iam_json_content}

json_name = json_name or "initial—tmp.json"
iam_migrate_json_path = os.path.join(settings.BASE_DIR, f"backend/iam_app/migration_json_files/{json_name}")
with open(iam_migrate_json_path, "w+") as f:
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 +196,90 @@ 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(
{"system": BK_IAM_SYSTEM_ID, "actions": action_infos, "resources": resource_infos}
resource_topo_auth_content.append(
{"system": env.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))
# 匹配{biz_name}_DBA(#{biz_id})这样格式的用户组
dba_group_pattern = re.compile(r"^.*?_DBA\(#[0-9]*\)$")
dba_groups = [group for group in data["results"] if dba_group_pattern.match(group["name"])]

# 刷新权限
for group in dba_groups:
try:
# 提取用户组ID和业务
group_id, name = group["id"], group["name"]
biz = AppCache.objects.get(bk_biz_name=name.split("_DBA")[0])
# 分配权限
assign_auth_to_group(iam, biz, group_id)
except Exception as e:
raise BaseIAMError(_("用户组{}刷新失败,错误信息:{}").format(group, e))
66 changes: 66 additions & 0 deletions dbm-ui/backend/iam_app/handlers/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# -*- 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, http_put


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

# 更新用户组名字和描述
def update_user_groups(self, system_id, group_id, data):
path = "/api/v2/open/management/systems/{system_id}/groups/{group_id}/".format(
system_id=system_id, group_id=group_id
)
ok, message, data = self._call_iam_api(http_put, 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
Loading
Loading