# 火山引擎 IAM 子账号管控工具 -- 深度研究报告 > 研究日期:2026-03-19 > 目标:通过火山引擎 Open API,实现对 IAM 子账号的全面管控,包括权限隔离、消费监控、告警、自动停用等功能。 --- ## 目录 1. [整体架构方案](#1-整体架构方案) 2. [API 认证与签名机制](#2-api-认证与签名机制) 3. [IAM 用户管理 API](#3-iam-用户管理-api) 4. [权限策略管理](#4-权限策略管理) 5. [API 密钥管理](#5-api-密钥管理) 6. [计费与消费查询 API](#6-计费与消费查询-api) 7. [预算与告警机制](#7-预算与告警机制) 8. [子账号自动停用/恢复方案](#8-子账号自动停用恢复方案) 9. [项目管理与资源隔离](#9-项目管理与资源隔离) 10. [SDK 与工具链](#10-sdk-与工具链) 11. [可执行实施方案](#11-可执行实施方案) 12. [限制与注意事项](#12-限制与注意事项) 13. [参考文档](#13-参考文档) --- ## 1. 整体架构方案 ### 1.1 核心需求 | 需求 | 实现方式 | 可行性 | |------|----------|--------| | 子账号不能看到主账号信息 | IAM 默认零权限 + 显式 Deny 策略 | **完全可行** | | 子账号仅有 Seedance 2.0 + TOS 权限 | 仅附加 ArkFullAccess + TOSFullAccess 策略 | **完全可行** | | 子账号能看到自己的账单 | 通过项目 + 标签维度,主账号代查并展示 | **部分可行**(见下方说明)| | 子账号不能看到其他账号消费/余额 | 不授予 billing/bss 权限 + 显式 Deny | **完全可行** | | 消费达到阈值发告警 | 定时轮询 Billing API + Webhook/飞书通知 | **完全可行** | | 消费达到阈值自动停用 | 轮询消费 + 调用 IAM API 停用用户和密钥 | **完全可行** | | 一键恢复子账号 | 调用 IAM API 重新启用 | **完全可行** | ### 1.2 架构图 ``` ┌──────────────────────────────────────────────────────┐ │ 管控工具 (后端服务) │ │ │ │ ┌─────────┐ ┌──────────┐ ┌────────────┐ │ │ │ IAM管理 │ │ 消费监控 │ │ 告警引擎 │ │ │ │ 模块 │ │ 模块 │ │ 模块 │ │ │ └────┬─────┘ └────┬─────┘ └────┬───────┘ │ │ │ │ │ │ │ ┌────▼─────────────▼─────────────▼───────┐ │ │ │ 火山引擎 Open API 调用层 │ │ │ │ (HMAC-SHA256 签名认证) │ │ │ └────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────┘ │ │ │ ┌────▼────┐ ┌─────▼─────┐ ┌────▼─────┐ │ IAM API │ │Billing API│ │CloudMonitor│ │iam.vol..│ │billing.vol│ │open.vol.. │ └─────────┘ └───────────┘ └────────────┘ ``` ### 1.3 关键发现 > **重要**:火山引擎的 IAM 子用户**没有独立的计费账户**。所有费用归属主账号。子账号的消费追踪需要通过**项目(Project)**或**标签(Tag)**维度来实现,由主账号通过 Billing API 查询后聚合展示。 --- ## 2. API 认证与签名机制 ### 2.1 签名算法 火山引擎使用 **HMAC-SHA256** 签名(类似 AWS Signature V4)。 **签名流程:** ``` 1. 构造规范请求 (Canonical Request) = HTTP方法 + 路径 + 排序后的查询参数 + 规范头部 + 签名头列表 + Body哈希 2. 构造待签名字符串 (String to Sign) = "HMAC-SHA256" + 时间戳 + 凭证范围 + SHA256(规范请求) 3. 派生签名密钥 kDate = HMAC-SHA256(SecretKey, 日期) kRegion = HMAC-SHA256(kDate, "cn-north-1") kService = HMAC-SHA256(kRegion, 服务名) kSigning = HMAC-SHA256(kService, "request") 4. 计算签名 Signature = Hex(HMAC-SHA256(kSigning, 待签名字符串)) ``` ### 2.2 完整 Python 签名实现 ```python import datetime import hashlib import hmac import requests from urllib.parse import quote class VolcengineClient: """火山引擎 API 客户端,处理 HMAC-SHA256 签名认证""" def __init__(self, ak: str, sk: str, service: str, host: str, region: str = "cn-north-1", version: str = "2018-01-01"): self.ak = ak self.sk = sk self.service = service self.host = host self.region = region self.version = version def _norm_query(self, params: dict) -> str: query = "" for key in sorted(params.keys()): if isinstance(params[key], list): for v in params[key]: query += quote(key, safe="-_.~") + "=" + quote(str(v), safe="-_.~") + "&" else: query += quote(key, safe="-_.~") + "=" + quote(str(params[key]), safe="-_.~") + "&" return query[:-1].replace("+", "%20") if query else "" def _hmac_sha256(self, key: bytes, content: str) -> bytes: return hmac.new(key, content.encode("utf-8"), hashlib.sha256).digest() def _hash_sha256(self, content: str) -> str: return hashlib.sha256(content.encode("utf-8")).hexdigest() def call(self, action: str, params: dict = None, body: str = "") -> dict: """调用火山引擎 API Returns: dict: API 响应。如果 ResponseMetadata 中包含 Error,会抛出 RuntimeError。 """ params = params or {} now = datetime.datetime.now(datetime.timezone.utc) x_date = now.strftime("%Y%m%dT%H%M%SZ") short_date = x_date[:8] x_content_sha256 = self._hash_sha256(body) all_params = {"Action": action, "Version": self.version, **params} signed_headers_str = "content-type;host;x-content-sha256;x-date" canonical_headers = ( f"content-type:application/x-www-form-urlencoded\n" f"host:{self.host}\n" f"x-content-sha256:{x_content_sha256}\n" f"x-date:{x_date}" ) query_string = self._norm_query(all_params) canonical_request = "\n".join([ "GET", "/", query_string, canonical_headers, "", signed_headers_str, x_content_sha256 ]) credential_scope = f"{short_date}/{self.region}/{self.service}/request" string_to_sign = "\n".join([ "HMAC-SHA256", x_date, credential_scope, self._hash_sha256(canonical_request) ]) k_date = self._hmac_sha256(self.sk.encode("utf-8"), short_date) k_region = self._hmac_sha256(k_date, self.region) k_service = self._hmac_sha256(k_region, self.service) k_signing = self._hmac_sha256(k_service, "request") signature = self._hmac_sha256(k_signing, string_to_sign).hex() headers = { "Host": self.host, "X-Date": x_date, "X-Content-Sha256": x_content_sha256, "Content-Type": "application/x-www-form-urlencoded", "Authorization": ( f"HMAC-SHA256 Credential={self.ak}/{credential_scope}, " f"SignedHeaders={signed_headers_str}, Signature={signature}" ) } # 重要:不能使用 requests.get(url, params=...),因为 requests 库会自行 # URL 编码参数,其编码方式可能与签名时使用的 _norm_query 不一致, # 导致签名校验失败(401 错误)。必须手动拼接 query string。 url = f"https://{self.host}/?{query_string}" r = requests.get(url, headers=headers) resp = r.json() # 检查 API 错误 error = resp.get("ResponseMetadata", {}).get("Error") if error: raise RuntimeError( f"Volcengine API Error [{action}]: " f"{error.get('Code', 'Unknown')} - {error.get('Message', '')}" ) return resp ``` --- ## 3. IAM 用户管理 API **服务端点:** `https://iam.volcengineapi.com/` **API 版本:** `2018-01-01` **服务代码:** `iam` ### 3.1 用户生命周期 API | Action | 说明 | 关键参数 | |--------|------|----------| | `CreateUser` | 创建子用户 | `UserName`(必填), `DisplayName`, `Email`, `MobilePhone` | | `GetUser` | 查询用户详情 | `UserName` | | `UpdateUser` | 更新用户信息 | `UserName`, `NewUserName`, `NewDisplayName` 等 | | `ListUsers` | 列出所有用户 | `Limit`, `Offset`(分页) | | `DeleteUser` | 删除用户 | `UserName` | ### 3.2 登录管理 API(控制台访问开关) | Action | 说明 | 关键参数 | |--------|------|----------| | `CreateLoginProfile` | 开通控制台登录 | `UserName`, `Password`, `LoginAllowed`, `PasswordResetRequired` | | `GetLoginProfile` | 查询登录状态 | `UserName` | | `UpdateLoginProfile` | **启用/停用用户** | `UserName`, `LoginAllowed`(true/false) | | `DeleteLoginProfile` | 删除登录能力 | `UserName` | > **关键能力**:`UpdateLoginProfile` + `LoginAllowed=false` 可以**停用子账号的控制台访问**,设为 `true` 即可**一键恢复**。 ### 3.3 GetLoginProfile 响应字段 | 字段 | 类型 | 说明 | |------|------|------| | `LoginAllowed` | Boolean | 是否允许登录 | | `LastLoginDate` | String | 最后登录时间 | | `LastLoginIp` | String | 最后登录 IP | | `LoginLocked` | Boolean | 是否被锁定 | | `SafeAuthFlag` | Boolean | 是否开启 MFA | ### 3.4 用户组管理 API | Action | 说明 | |--------|------| | `CreateGroup` | 创建用户组 | | `AddUserToGroup` | 添加用户到组 | | `RemoveUserFromGroup` | 移出用户组 | | `ListGroupsForUser` | 查询用户所在组 | | `ListUsersForGroup` | 查询组内用户 | --- ## 4. 权限策略管理 ### 4.1 策略操作 API | Action | 说明 | |--------|------| | `CreatePolicy` | 创建自定义策略 | | `GetPolicy` | 查询策略详情 | | `UpdatePolicy` | 更新策略 | | `DeletePolicy` | 删除策略 | | `ListPolicies` | 列出所有策略 | | `AttachUserPolicy` | 将策略附加到用户 | | `DetachUserPolicy` | 从用户分离策略 | | `ListAttachedUserPolicies` | 查询用户已附加的策略 | | `AttachPolicyInProject` | 在项目范围内附加策略 | | `DetachPolicyInProject` | 在项目范围内分离策略 | ### 4.2 系统预置策略(关键) | 策略名 | 适用场景 | 说明 | |--------|----------|------| | `ArkFullAccess` | Seedance 2.0 | 方舟平台完整管理权限(含模型、端点、微调) | | `ArkStandardGlobalAccess` | Seedance 2.0 | 标准使用权限(不含模型上线) | | `ArkReadOnlyAccess` | Seedance 2.0 | 只读权限 | | `TOSFullAccess` | 对象存储 | TOS 完整管理权限 | | `TOSReadOnlyAccess` | 对象存储 | TOS 只读权限 | | `AccessKeySelfManageAccess` | API 密钥 | 用户仅能管理自己的 API 密钥 | ### 4.3 策略文档格式 ```json { "Statement": [ { "Effect": "Allow | Deny", "Action": ["服务代码:操作名称"], "Resource": ["trn:服务代码:区域:账号ID:资源路径"], "Condition": { "条件运算符": { "条件键": ["值"] } } } ] } ``` **TRN(资源名称)格式:** `trn:${ServiceCode}:${Region}:${AccountId}:${ResourcePath}` 示例: - IAM 用户:`trn:iam::2100000000001:user/Bob` - TOS 存储桶:`trn:tos:::my-bucket` - TOS 对象:`trn:tos:::my-bucket/path/*` ### 4.4 推荐的子账号策略配置 #### 策略一:允许 Seedance 2.0 + TOS(使用系统策略) **方案 A -- 全局授权**(不需要项目隔离时): ``` AttachUserPolicy: PolicyName=ArkFullAccess, PolicyType=System AttachUserPolicy: PolicyName=TOSFullAccess, PolicyType=System AttachUserPolicy: PolicyName=AccessKeySelfManageAccess, PolicyType=System ``` **方案 B -- 项目级授权**(推荐,需要隔离不同子账号的资源): ``` AttachUserPolicy: PolicyName=AccessKeySelfManageAccess, PolicyType=System # 全局 AttachPolicyInProject: PolicyName=ArkFullAccess, ProjectName=DeptA-Project # 限定在项目内 AttachPolicyInProject: PolicyName=TOSFullAccess, ProjectName=DeptA-Project # 限定在项目内 ``` > **注意**:方案 A 和方案 B 不能混用。如果同时全局附加和项目级附加同一策略,全局策略会使项目限制失效。 #### 策略二:禁止查看主账号信息(自定义 Deny 策略) > **注意**:此策略故意**排除了** `iam:CreateAccessKey`、`iam:UpdateAccessKey`、`iam:DeleteAccessKey`、`iam:ListAccessKeys`、`iam:GetAccessKeyLastUsed` 等密钥自管理操作,以避免与 `AccessKeySelfManageAccess` 策略冲突。因为 **Deny 优先于 Allow**,如果这里 deny 了 `iam:*`,子账号将无法管理自己的 API 密钥。 ```json { "Statement": [ { "Effect": "Deny", "Action": [ "iam:ListUsers", "iam:GetUser", "iam:ListGroups", "iam:GetGroup", "iam:ListRoles", "iam:GetRole", "iam:ListPolicies", "iam:GetPolicy", "iam:ListAttachedUserPolicies", "iam:ListAttachedRolePolicies", "iam:ListEntitiesForPolicy", "iam:GetLoginProfile", "iam:GetSecurityConfig", "iam:CreateUser", "iam:UpdateUser", "iam:DeleteUser", "iam:CreateGroup", "iam:UpdateGroup", "iam:DeleteGroup", "iam:CreateRole", "iam:UpdateRole", "iam:DeleteRole", "iam:CreatePolicy", "iam:UpdatePolicy", "iam:DeletePolicy", "iam:AttachUserPolicy", "iam:DetachUserPolicy", "iam:AttachRolePolicy", "iam:DetachRolePolicy", "iam:CreateLoginProfile", "iam:UpdateLoginProfile", "iam:DeleteLoginProfile", "iam:AddUserToGroup", "iam:RemoveUserFromGroup", "iam:ListUsersForGroup", "iam:ListGroupsForUser", "iam:SetSecurityConfig", "iam:CreateServiceLinkedRole", "iam:DeleteServiceLinkedRole", "iam:AttachUserGroupPolicy", "iam:DetachUserGroupPolicy", "iam:ListAttachedUserGroupPolicies", "iam:AssumeRole" ], "Resource": ["*"] }, { "Effect": "Deny", "Action": [ "billing:*", "bss:*" ], "Resource": ["*"] }, { "Effect": "Deny", "Action": [ "organization:*" ], "Resource": ["*"] } ] } ``` > **原理**:IAM 子用户**默认没有任何权限**。即使不加 Deny 策略,子用户也看不到主账号信息。但显式 Deny 可以防止其他策略意外授权。Deny 优先级始终高于 Allow。 > > **关键设计**:Deny 策略中明确列出了要禁止的 IAM 操作,而**没有使用 `iam:*` 通配符**。这样不会阻断 `AccessKeySelfManageAccess` 授予的密钥自管理能力(`iam:CreateAccessKey`、`iam:UpdateAccessKey`、`iam:DeleteAccessKey`、`iam:ListAccessKeys`)。 #### 策略三:允许用户管理自己的 API 密钥 已有系统预置策略 `AccessKeySelfManageAccess`,直接附加即可。 #### 策略四:TOS 限定到指定存储桶 ```json { "Statement": [ { "Effect": "Allow", "Action": ["tos:*"], "Resource": [ "trn:tos:::department-bucket", "trn:tos:::department-bucket/*" ] }, { "Effect": "Allow", "Action": ["tos:ListBuckets"], "Resource": ["*"] } ] } ``` --- ## 5. API 密钥管理 | Action | 说明 | 关键参数 | |--------|------|----------| | `CreateAccessKey` | 创建 API 密钥对 | `UserName`(可选,不填=为自己创建) | | `ListAccessKeys` | 列出用户的密钥 | `UserName` | | `UpdateAccessKey` | **启用/停用密钥** | `AccessKeyId`, `Status`(active/inactive) | | `DeleteAccessKey` | 删除密钥 | `AccessKeyId` | | `GetAccessKeyLastUsed` | 查询密钥最后使用 | `AccessKeyId` | ### 重要限制 - **每个用户最多 2 个 API 密钥** - **SecretAccessKey 仅在创建时返回一次**,之后无法再获取 - 停用密钥后,使用该密钥的所有 API 调用将立即失败 ### 停用子账号的 API 访问 ```python # 停用密钥 = 立即切断子账号的所有 API 调用能力 iam_client.call("UpdateAccessKey", { "AccessKeyId": "AKLT****", "Status": "inactive", "UserName": "sub_user_1" }) ``` ### 恢复子账号的 API 访问 ```python # 恢复密钥 = 一键恢复 iam_client.call("UpdateAccessKey", { "AccessKeyId": "AKLT****", "Status": "active", "UserName": "sub_user_1" }) ``` --- ## 6. 计费与消费查询 API **服务端点:** `https://billing.volcengineapi.com` **API 版本:** `2022-01-01` **服务代码:** `billing` **QPS 限制:** 5 QPS ### 6.1 账单查询 API | Action | 说明 | 粒度 | |--------|------|------| | `ListBillOverviewByCategory` | 按类别汇总 | 月 | | `ListBillOverviewByProd` | 按产品汇总 | 月 | | `ListBill` | 账单流水 | 月 | | `ListBillDetail` | **明细账单**(最细粒度) | 日/月 | | `ListSplitBillDetail` | 分账账单(按资源拆分) | 月 | | `ListAmortizedCostBillDaily` | 每日摊销成本 | 日 | ### 6.2 ListBillDetail 关键参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `BillPeriod` | String | 是 | 格式 YYYY-MM,近 24 个月 | | `Limit` | Integer | 是 | 每页数量 1-300 | | `Offset` | Integer | 否 | 分页偏移 | | `OwnerID` | Array[Long] | 否 | 按资源拥有者筛选 | | `Product` | Array[String] | 否 | 按产品筛选 | | `GroupTerm` | Integer | 否 | 0=明细, 1=实例, 2=产品, 3=账号 | | `GroupPeriod` | Integer | 否 | 0=账期, 1=日, 2=详情 | | `ExpenseDate` | String | 否 | 特定日期(需 GroupPeriod=1) | | `InstanceNo` | String | 否 | 实例 ID 筛选 | ### 6.3 按子账号追踪消费的方法 > **核心问题**:IAM 子账号没有独立的计费维度。不能直接按 IAM UserName 查询消费。 **可行方案:** | 方案 | 实现方式 | 精确度 | |------|----------|--------| | **按项目追踪** | 为每个子账号/部门创建独立项目,资源都放在项目中 | 高 | | **按标签追踪** | 资源打上子账号标签(如 `owner=sub_user_1`) | 高 | | **按 Ark 端点追踪** | Seedance/方舟按 Endpoint 分账(ListSplitBillDetail) | 中 | | **按 TOS 存储桶追踪** | TOS 按 Bucket 分账 | 高 | **推荐方案:项目 + 标签 双维度** ``` 1. 创建项目 "DeptA-Project" 2. 子账号的权限限定在该项目范围内 (AttachPolicyInProject) 3. 资源打标签 tag: {"department": "DeptA", "owner": "sub_user_1"} 4. 通过 ListBillDetail + ListSplitBillDetail 按项目/标签筛选消费 ``` ### 6.4 账户余额查询 ```python billing_client = VolcengineClient(AK, SK, "billing", "billing.volcengineapi.com", version="2022-01-01") # 查询账户余额 balance = billing_client.call("QueryBalanceAcct") # 返回:可用余额、冻结金额等 ``` ### 6.5 数据时效性 | 数据类型 | 可用时间 | |----------|----------| | 上月完整账单 | 每月 2 日 12:00 | | 日粒度账单 | T+1 ~ T+2 天 | | 分账账单 | ~2 天延迟 | | 摊销成本 | ~2 天延迟 | | **实时账单** | **不支持** | --- ## 7. 预算与告警机制 ### 7.1 预算管理 API(Billing 模块) | Action | 说明 | |--------|------| | `CreateBudget` | 创建预算 | | `UpdateBudget` | 更新预算 | | `ListBudget` | 查询预算列表 | | `QueryBudgetDetail` | 查询预算详情 | | `DeleteBudget` | 删除预算 | | `ListBudgetAmountByBudgetID` | 查询预算金额 | | `ListRecipientInformation` | 查询告警接收人 | **预算可按以下维度筛选:** - 区域、产品、标签、项目、账号 OwnerID、付款人 PayerID、可用区、计费模式 ### 7.2 CloudMonitor Webhook 告警 **端点:** `https://open.volcengineapi.com?Action={Action}&Version=2018-01-01` | Action | 说明 | |--------|------| | `CreateRule` | 创建告警规则(支持 Webhook) | | `CreateWebhook` | 配置 Webhook 回调地址 | | `CreateContacts` | 添加告警联系人 | | `CreateContactGroup` | 创建通知组 | **支持的通知渠道:** - 站内信(默认开启) - Email - SMS - 飞书(Feishu) - 钉钉(DingTalk) - 企业微信 - 自定义 Webhook(HTTP POST 回调) ### 7.3 Ark 推理限额(Seedance 专属) 火山方舟(Ark)平台有**推理限额**功能: - 可设置每个模型的最大 Token 消耗量 - **达到限额后服务自动暂停** - 最小调整间隔 2 小时 - 仅支持在线推理(不含批量) - 目前仅支持通过控制台设置,**暂无公开 API** ### 7.4 自建告警方案(推荐) 由于火山原生的预算告警仅支持站内信/邮件/短信通知,不支持自动停用。需要自建: ``` ┌──────────────────────────────────────────────┐ │ 定时任务 (每小时/每天) │ │ │ │ 1. 调用 ListBillDetail 查询各子账号消费 │ │ 2. 与预设阈值对比 │ │ 3. 达到告警阈值 → 发送通知 │ │ 4. 达到停用阈值 → 调用 IAM API 停用用户 │ └──────────────────────────────────────────────┘ ``` --- ## 8. 子账号自动停用/恢复方案 ### 8.1 完全停用子账号(保留账号,可恢复) 需要**同时**执行两个操作才能完全停用: ```python def get_user_access_keys(iam_client, username: str) -> list: """获取用户的所有 AccessKey ID""" result = iam_client.call("ListAccessKeys", {"UserName": username}) keys = result.get("Result", {}).get("AccessKeyMetadata", []) return [k["AccessKeyId"] for k in keys] def disable_sub_user(iam_client, username: str, access_key_ids: list = None): """完全停用子账号(保留账号,可一键恢复)""" # 0. 如果未传入 access_key_ids,自动查询 if access_key_ids is None: access_key_ids = get_user_access_keys(iam_client, username) # 1. 停用控制台登录 iam_client.call("UpdateLoginProfile", { "UserName": username, "LoginAllowed": "false" }) # 2. 停用所有 API 密钥 for ak_id in access_key_ids: iam_client.call("UpdateAccessKey", { "AccessKeyId": ak_id, "Status": "inactive", "UserName": username }) print(f"用户 {username} 已完全停用(控制台 + {len(access_key_ids)} 个 API 密钥)") ``` ### 8.2 一键恢复子账号 ```python def enable_sub_user(iam_client, username: str, access_key_ids: list = None): """一键恢复子账号""" # 0. 如果未传入 access_key_ids,自动查询 if access_key_ids is None: access_key_ids = get_user_access_keys(iam_client, username) # 1. 恢复控制台登录 iam_client.call("UpdateLoginProfile", { "UserName": username, "LoginAllowed": "true" }) # 2. 恢复所有 API 密钥 for ak_id in access_key_ids: iam_client.call("UpdateAccessKey", { "AccessKeyId": ak_id, "Status": "active", "UserName": username }) print(f"用户 {username} 已恢复(控制台 + {len(access_key_ids)} 个 API 密钥)") ``` ### 8.3 停用 vs 删除的区别 | 操作 | 效果 | 可恢复 | |------|------|--------| | `UpdateLoginProfile(LoginAllowed=false)` | 停用控制台登录 | 一键恢复 | | `UpdateAccessKey(Status=inactive)` | 停用 API 访问 | 一键恢复 | | `DetachUserPolicy` | 移除权限但保留用户 | 重新附加即可 | | `DeleteUser` | **永久删除用户** | **不可恢复** | --- ## 9. 项目管理与资源隔离 ### 9.1 项目管理 API **端点:** `https://open.volcengineapi.com` | Action | 说明 | |--------|------| | `CreateProject` | 创建项目 | | `ListProjects` | 列出项目 | | `GetProject` | 获取项目详情 | | `UpdateProject` | 更新项目 | | `DeleteProject` | 删除项目 | | `ListProjectResources` | 列出项目中的资源 | | `MoveProjectResource` | 在项目间移动资源 | | `ListProjectIdentities` | 列出项目中的用户/角色 | ### 9.2 项目级权限授权 ```python # 在项目范围内授权(子账号只能访问该项目下的资源) iam_client.call("AttachPolicyInProject", { "UserName": "sub_user_1", "PolicyName": "ArkFullAccess", "PolicyType": "System", "ProjectName": "DeptA-Project" }) ``` **效果:** 子账号仅能操作 `DeptA-Project` 项目下的 Ark/Seedance 资源,无法看到其他项目的内容。 ### 9.3 标签管理 API | Action | 说明 | |--------|------| | `TagResources` | 给资源打标签 | | `UntagResources` | 移除标签 | | `ListTagsForResources` | 查询资源标签 | **标签用于:** - 资源分组与管理 - 按标签筛选账单(在 ListBillDetail 响应中的 `Tag` 字段) - IAM 条件策略(基于标签的访问控制) --- ## 10. SDK 与工具链 ### 10.1 推荐 SDK | 语言 | 包名 | 安装 | 覆盖 IAM/Billing | |------|------|------|-------------------| | **Python**(推荐) | `volcengine-python-sdk` | `pip install volcengine-python-sdk` | 是 | | Go | `volcengine-go-sdk` | `go get github.com/volcengine/volcengine-go-sdk` | 是 | | Node.js | `@volcengine/openapi` | `npm install @volcengine/openapi` | 是 | | Java | `volcengine-java-sdk` | Maven | 是 | ### 10.2 Python SDK 使用示例 ```python import volcenginesdkcore import volcenginesdkiam # 配置 configuration = volcenginesdkcore.Configuration() configuration.ak = "YOUR_AK" configuration.sk = "YOUR_SK" configuration.region = "cn-beijing" volcenginesdkcore.Configuration.set_default(configuration) # IAM 操作 iam_api = volcenginesdkiam.IAMApi( volcenginesdkcore.ApiClient(configuration) ) # 列出用户 users = iam_api.list_users(volcenginesdkiam.ListUsersRequest( limit=100, offset=0 )) ``` ### 10.3 CLI 工具 ```bash # 安装 Volcengine CLI # 从 https://github.com/volcengine/volcengine-cli/releases 下载 # 配置 ve configure set --profile default --region cn-beijing \ --access-key YOUR_AK --secret-key YOUR_SK # 使用 ve iam ListUsers ve iam CreateUser --UserName "sub_user_1" --DisplayName "Sub User 1" ve billing ListBillDetail --BillPeriod "2026-03" --Limit 100 ``` --- ## 11. 可执行实施方案 ### 第一阶段:基础搭建 #### Step 1:创建子账号 ```python # 创建 IAM 客户端 iam = VolcengineClient(AK, SK, "iam", "iam.volcengineapi.com") # 创建子用户 iam.call("CreateUser", { "UserName": "dept_a_user", "DisplayName": "部门A用户", "Email": "dept_a@company.com", "MobilePhone": "+8618000000000" }) # 开通控制台登录 iam.call("CreateLoginProfile", { "UserName": "dept_a_user", "Password": "Initial@Pass123", "LoginAllowed": "true", "PasswordResetRequired": "true" }) # 创建 API 密钥(记录返回的 SecretAccessKey!) result = iam.call("CreateAccessKey", {"UserName": "dept_a_user"}) # result["Result"]["AccessKey"]["SecretAccessKey"] -- 仅此一次! ``` #### Step 2:配置权限 > **重要**:如果要通过项目隔离资源(Step 4),**不要**在此处全局附加 `ArkFullAccess` / `TOSFullAccess`, > 否则全局策略会覆盖项目级限制,子账号将能访问所有项目的资源。 > 应当仅在项目范围内授权(见 Step 4),或者如果不需要项目隔离则可以全局附加。 ```python # 方案 A:不需要项目隔离时,全局授权 # iam.call("AttachUserPolicy", { # "UserName": "dept_a_user", # "PolicyName": "ArkFullAccess", # "PolicyType": "System" # }) # iam.call("AttachUserPolicy", { # "UserName": "dept_a_user", # "PolicyName": "TOSFullAccess", # "PolicyType": "System" # }) # 方案 B(推荐):需要项目隔离时,此处只附加密钥自管理策略 # Ark 和 TOS 的权限在 Step 4 中通过 AttachPolicyInProject 在项目范围内授权 # 允许自行管理 API 密钥(此策略需全局附加,不受项目限制) iam.call("AttachUserPolicy", { "UserName": "dept_a_user", "PolicyName": "AccessKeySelfManageAccess", "PolicyType": "System" }) ``` #### Step 3:创建并附加 Deny 策略 ```python import json deny_policy = { "Statement": [ { "Effect": "Deny", "Action": [ "iam:ListUsers", "iam:GetUser", "iam:ListGroups", "iam:GetGroup", "iam:ListRoles", "iam:GetRole", "iam:ListPolicies", "iam:GetPolicy", "iam:ListAttachedUserPolicies", "iam:ListAttachedRolePolicies", "iam:ListEntitiesForPolicy", "iam:GetLoginProfile", "iam:GetSecurityConfig", "iam:CreateUser", "iam:UpdateUser", "iam:DeleteUser", "iam:CreateGroup", "iam:UpdateGroup", "iam:DeleteGroup", "iam:CreateRole", "iam:UpdateRole", "iam:DeleteRole", "iam:CreatePolicy", "iam:UpdatePolicy", "iam:DeletePolicy", "iam:AttachUserPolicy", "iam:DetachUserPolicy", "iam:AttachRolePolicy", "iam:DetachRolePolicy", "iam:CreateLoginProfile", "iam:UpdateLoginProfile", "iam:DeleteLoginProfile", "iam:AddUserToGroup", "iam:RemoveUserFromGroup", "iam:ListUsersForGroup", "iam:ListGroupsForUser", "iam:SetSecurityConfig", "iam:CreateServiceLinkedRole", "iam:DeleteServiceLinkedRole", "iam:AttachUserGroupPolicy", "iam:DetachUserGroupPolicy", "iam:ListAttachedUserGroupPolicies", "iam:AssumeRole" ], "Resource": ["*"] }, { "Effect": "Deny", "Action": ["billing:*", "bss:*"], "Resource": ["*"] }, { "Effect": "Deny", "Action": ["organization:*"], "Resource": ["*"] } ] } # 注意:PolicyDocument 不要额外 URL 编码,VolcengineClient._norm_query 会自动编码 iam.call("CreatePolicy", { "PolicyName": "DenyAdminAndBilling", "Description": "禁止访问 IAM 管理和计费信息", "PolicyDocument": json.dumps(deny_policy) }) iam.call("AttachUserPolicy", { "UserName": "dept_a_user", "PolicyName": "DenyAdminAndBilling", "PolicyType": "Custom" }) ``` ### 第二阶段:消费监控 #### Step 4:创建项目并分配 ```python # 使用项目管理 API(通过 open.volcengineapi.com) # 注意:项目管理的 service 签名名称需要根据实际情况确认, # 可能是 "resource_manager" 或其他名称,建议先用 API Explorer 测试 project_client = VolcengineClient(AK, SK, "resource_manager", "open.volcengineapi.com") # 创建项目 project_client.call("CreateProject", { "ProjectName": "DeptA-Project", "Description": "部门A专属项目" }) # 在项目范围内授权(子账号只能操作此项目下的 Ark 和 TOS 资源) iam.call("AttachPolicyInProject", { "UserName": "dept_a_user", "PolicyName": "ArkFullAccess", "PolicyType": "System", "ProjectName": "DeptA-Project" }) iam.call("AttachPolicyInProject", { "UserName": "dept_a_user", "PolicyName": "TOSFullAccess", "PolicyType": "System", "ProjectName": "DeptA-Project" }) ``` #### Step 5:消费查询脚本 ```python billing = VolcengineClient(AK, SK, "billing", "billing.volcengineapi.com", version="2022-01-01") def get_user_spending(bill_period: str, project_name: str = None) -> float: """查询指定项目/用户的消费金额(带分页处理)""" total = 0.0 offset = 0 page_size = 300 while True: params = { "BillPeriod": bill_period, "Limit": str(page_size), "Offset": str(offset), "GroupTerm": "0", # 明细级别(非聚合),确保 Project 字段可用 "GroupPeriod": "0", # 按账期 "NeedRecordNum": "1", } result = billing.call("ListBillDetail", params) items = result.get("Result", {}).get("List", []) record_num = int(result.get("Result", {}).get("Total", 0)) for item in items: # 按项目筛选 if project_name and item.get("Project") != project_name: continue total += float(item.get("PayableAmount", "0")) # 分页:如果还有更多数据,继续查询 offset += page_size if offset >= record_num or not items: break return total ``` ### 第三阶段:告警与自动停用 #### Step 6:消费监控与告警服务 ```python import time import logging import requests as http_requests logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger("volcengine_monitor") # 配置 ALERT_THRESHOLD = 1000.0 # 告警阈值(元) DISABLE_THRESHOLD = 5000.0 # 停用阈值(元) CHECK_INTERVAL = 3600 # 检查间隔(秒),每小时 FEISHU_WEBHOOK = "https://open.feishu.cn/open-apis/bot/v2/hook/YOUR_HOOK_ID" MANAGED_USERS = [ { "username": "dept_a_user", "project": "DeptA-Project", "alert_threshold": ALERT_THRESHOLD, "disable_threshold": DISABLE_THRESHOLD, } ] # 记录已停用的用户,避免重复停用和重复告警 _disabled_users: set = set() # 记录已发送告警的用户,避免每轮都重复告警 _alerted_users: set = set() def send_feishu_alert(title: str, content: str): """发送飞书告警""" payload = { "msg_type": "interactive", "card": { "header": {"title": {"tag": "plain_text", "content": title}}, "elements": [ {"tag": "div", "text": {"tag": "plain_text", "content": content}} ] } } try: resp = http_requests.post(FEISHU_WEBHOOK, json=payload, timeout=10) resp.raise_for_status() except Exception as e: logger.error(f"飞书告警发送失败: {e}") def check_and_alert(): """检查消费并触发告警/停用""" from datetime import datetime bill_period = datetime.now().strftime("%Y-%m") for user in MANAGED_USERS: username = user["username"] # 已停用的用户跳过(避免重复停用和告警) if username in _disabled_users: logger.info(f"用户 {username} 已处于停用状态,跳过检查") continue try: spending = get_user_spending(bill_period, user["project"]) except RuntimeError as e: logger.error(f"查询用户 {username} 消费失败: {e}") continue logger.info(f"用户 {username} 本月消费: ¥{spending:.2f}") if spending >= user["disable_threshold"]: # 自动停用(自动查询该用户的 AccessKey) try: disable_sub_user(iam, username) _disabled_users.add(username) send_feishu_alert( "【紧急】子账号已自动停用", f"用户 {username} 本月消费 ¥{spending:.2f}," f"已达到停用阈值 ¥{user['disable_threshold']:.2f},已自动停用。\n" f"如需恢复,请在管控工具中操作。" ) except RuntimeError as e: logger.error(f"停用用户 {username} 失败: {e}") send_feishu_alert("【错误】自动停用失败", f"用户 {username} 消费 ¥{spending:.2f},停用失败: {e}") elif spending >= user["alert_threshold"] and username not in _alerted_users: # 发送告警(每账期只告警一次) _alerted_users.add(username) send_feishu_alert( "【警告】子账号消费告警", f"用户 {username} 本月消费 ¥{spending:.2f}," f"已达到告警阈值 ¥{user['alert_threshold']:.2f}。\n" f"停用阈值:¥{user['disable_threshold']:.2f}" ) # 主循环(生产环境建议用 cron 或 APScheduler) def main(): logger.info("消费监控服务启动") while True: try: check_and_alert() except Exception as e: logger.error(f"监控服务异常: {e}") send_feishu_alert("【错误】监控服务异常", str(e)) time.sleep(CHECK_INTERVAL) ``` > **注意**:每月初应重置 `_disabled_users` 和 `_alerted_users` 集合(或使用持久化存储), > 否则跨月后状态不正确。生产环境建议将状态存入数据库或 Redis。 #### Step 7:管理后台 API 接口设计 ``` # 用户管理 GET /api/iam/users # 列出所有子账号 POST /api/iam/users # 创建子账号 GET /api/iam/users/{username} # 查询子账号详情 PUT /api/iam/users/{username} # 更新子账号 DELETE /api/iam/users/{username} # 删除子账号 # 启停控制 POST /api/iam/users/{username}/disable # 停用子账号 POST /api/iam/users/{username}/enable # 恢复子账号 # 权限管理 GET /api/iam/users/{username}/policies # 查看子账号权限 POST /api/iam/users/{username}/policies # 附加权限 DELETE /api/iam/users/{username}/policies # 移除权限 # 密钥管理 GET /api/iam/users/{username}/access-keys # 列出密钥 POST /api/iam/users/{username}/access-keys # 创建密钥 PUT /api/iam/users/{username}/access-keys/{id} # 启停密钥 # 消费查询 GET /api/billing/users/{username}/spending # 查询子账号消费 GET /api/billing/overview # 消费总览 # 告警配置 GET /api/alerts/config # 查看告警配置 PUT /api/alerts/config # 更新阈值配置 GET /api/alerts/history # 告警历史 ``` --- ## 12. 限制与注意事项 ### 12.1 关键限制 | 限制项 | 说明 | |--------|------| | IAM 子账号无独立计费 | 所有费用归主账号,需通过项目/标签追踪 | | Billing API 无实时数据 | 最快 T+1 天粒度,有 1-2 天延迟 | | 每用户最多 2 个 API 密钥 | 无法创建更多 | | SecretKey 仅返回一次 | 创建后立即保存 | | Billing API QPS 限制 5 | 批量查询需注意限流 | | Ark 推理限额无公开 API | 目前仅支持控制台操作 | | 预算告警仅通知不自动执行 | 需自建自动停用逻辑 | ### 12.2 安全建议 1. **主账号 AK/SK 务必安全存储**,建议使用环境变量或密钥管理服务 2. **定期轮换 API 密钥**,利用 `GetAccessKeyLastUsed` 检查不活跃的密钥 3. **遵循最小权限原则**,只授予必要的权限 4. **显式 Deny 策略优先**,防止权限漏洞 5. **监控日志**,使用 CloudTrail 审计 API 调用 ### 12.3 消费监控的精确度问题 由于账单数据有 1-2 天延迟,消费监控存在滞后。应对策略: - 设置更保守的阈值(如实际想控制 5000 元,告警阈值设 3000,停用阈值设 4000) - 结合 Ark 推理限额功能(自动暂停,无延迟) - 高频轮询(如每小时)以尽早发现异常 --- ## 13. 参考文档 ### 官方文档 | 文档 | URL | |------|-----| | IAM API 概览 | https://www.volcengine.com/docs/6257/65842 | | IAM 基本概念 | https://www.volcengine.com/docs/6257/64963 | | 创建用户并授权 | https://www.volcengine.com/docs/6257/94013 | | CreateAccessKey | https://www.volcengine.com/docs/6257/65000 | | AttachUserPolicy | https://www.volcengine.com/docs/6257/65029 | | LoginProfile 管理 | https://www.volcengine.com/docs/6257/65013 | | 策略概述 | https://www.volcengine.com/docs/6257/65058 | | 策略基本结构 | https://www.volcengine.com/docs/6257/65059 | | 系统预置策略 | https://www.volcengine.com/docs/6257/1253730 | | 自定义策略 | https://www.volcengine.com/docs/6257/1158323 | | Billing API 概览 | https://www.volcengine.com/docs/6269/1165275 | | ListBillDetail | https://www.volcengine.com/docs/6269/1127842 | | 预算管理 | https://www.volcengine.com/docs/6269/1165274 | | 分账账单 | https://www.volcengine.com/docs/6269/177196 | | 计费权限管理 | https://www.volcengine.com/docs/6269/1186807 | | 项目管理 | https://www.volcengine.com/docs/6649/166155 | | TOS IAM 策略 | https://www.volcengine.com/docs/6349/102133 | | Ark IAM 教程 | https://www.volcengine.com/docs/82379/1263493 | | Ark 推理限额 | https://www.volcengine.com/docs/82379/1159200 | | CloudMonitor API | https://www.volcengine.com/docs/6408/78940 | | API 签名方法 | https://www.volcengine.com/docs/6369/67269 | ### GitHub 资源 | 资源 | URL | |------|-----| | Volcengine GitHub | https://github.com/volcengine | | Python SDK | https://github.com/volcengine/volcengine-python-sdk | | Go SDK | https://github.com/volcengine/volcengine-go-sdk | | Node.js SDK | https://github.com/volcengine/volc-sdk-nodejs | | OpenAPI Demos | https://github.com/volcengine/volc-openapi-demos | | CLI 工具 | https://github.com/volcengine/volcengine-cli | | TOS Python SDK | https://github.com/volcengine/ve-tos-python-sdk | ### 其他资源 | 资源 | URL | |------|-----| | API Explorer | https://api.volcengine.com/api-docs | | SDK Center | https://api.volcengine.com/api-sdk | | PyPI (新 SDK) | https://pypi.org/project/volcengine-python-sdk/ | | npm | https://www.npmjs.com/package/@volcengine/openapi | --- > **下一步行动**:基于此报告,可以开始开发管控工具的后端服务。建议使用 Python + `volcengine-python-sdk`,先实现核心的 IAM 管理和消费监控功能,再逐步集成到 AirDrama 管理后台。