# 火山引擎 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 策略 | **完全可行** | | 子账号能看到自己的账单 | 通过 AirGate 按多项目聚合查询,主账号代查展示,可按项目查看明细 | **完全可行** | | 子账号不能看到其他账号消费/余额 | 不授予 billing/bss 权限 + 显式 Deny | **完全可行** | | 消费达到阈值发告警 | 额度划拨制 + 阶梯式告警(50%/80%/90%)+ 飞书通知 | **完全可行** | | 消费达到阈值自动停用 | 消费达到已划拨额度 100% 时自动停用 | **完全可行** | | 一键恢复子账号 | 调用 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 查询消费。 **AirGate 采用的方案:多项目聚合追踪** 一个子账号可以关联多个火山项目,每个项目有独立的监测开关。消费追踪按**所有开启监测的项目消费之和**计算。 ``` 子账号 (seaislee) ├── 项目A: Seedance-团队1 ← 开启监测 → 消费 ¥30,000 ├── 项目B: Seedance-团队2 ← 开启监测 → 消费 ¥20,000 ├── 项目C: Seedance-团队3 ← 开启监测 → 消费 ¥15,000 ├── 项目D: 测试项目 ← 关闭监测(不计入) └── 项目E: 内部工具 ← 关闭监测(不计入) 累计消费 = 项目A + 项目B + 项目C = ¥65,000(仅算开启监测的) 已划拨额度: ¥100,000 使用率: 65% → 50% 告警已触发 ``` **典型使用场景:** - 一个部门子账号下,每个团队各创建一个火山项目 - 每个项目下各有一个 Seedance 2.0 API 端点 - 管理员可按需开关某些项目的监测(如测试项目不计费) - 告警和自动停用基于所有开启项目的消费总和 vs 划拨额度 **消费查询方式:** 对每个开启监测的项目分别调用 `ListBillDetail`(按 Project 字段筛选),累加得出总消费。同时记录每个项目的独立消费,前端可展开查看明细。 ### 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 AirGate 自建方案:额度划拨制 + 阶梯式告警 由于火山原生的预算告警仅支持站内信/邮件/短信通知,不支持自动停用。AirGate 采用**额度划拨制**自建: ``` 主账号通过 AirGate 给子账号划拨额度(如 10 万元) │ ▼ 定时任务每小时查询 Billing API 遍历子账号下所有开启监测的项目 → 分别查询消费 → 累加得出总消费 │ ▼ 总消费对比已划拨额度 │ ├── 消费达到额度 50% → 飞书告警 ├── 消费达到额度 80% → 飞书告警 ├── 消费达到额度 90% → 飞书告警 └── 消费达到额度 100% → 自动停用子账号 + 飞书告警 额度用完 → 主账号在 AirGate 追加额度 → 告警状态自动重置 → 恢复子账号 额度给多了 → 主账号在 AirGate 扣减额度 → 告警状态自动重置 ``` **关键设计:** - **多项目聚合**:一个子账号可关联多个火山项目,每个项目有独立监测开关。消费 = 所有开启监测的项目消费之和 - **项目明细可查**:前端可展开查看每个项目的独立消费,便于分析哪个团队/项目花得多 - **非月度制**:额度不按月重置,是一次性划拨,用完再充 - **可追加可扣减**:主账号可随时追加额度(+5万)或扣减额度(-3万),支持灵活调整 - **扣减保护**:扣减后总额度不能低于已消费金额(否则会立即触发停用) - **阶梯式告警**:每个子账号可自定义告警百分比(如 [50, 80, 90]),每档只通知一次 - **额度变更即重置告警**:追加或扣减额度后,已触发的告警状态自动清空,按新的使用率重新计算 - **累计消费**:跨月累计,通过 Billing 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(与 IAM 共用端点,但 Version 不同) project_client = VolcengineClient(AK, SK, "iam", "iam.volcengineapi.com", version="2021-08-01") # 创建项目 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 ``` ### 第三阶段:告警与自动停用(已在 AirGate 中实现) > 以下逻辑已通过 AirGate 管理平台实现,不再需要手写脚本。 > 详见 `backend/utils/scheduler.py` 和 `backend/apps/monitor/views.py`。 **AirGate 实现的核心流程:** 1. 主账号通过界面给子账号**划拨额度**(如 10 万元) 2. 子账号下挂多个火山项目,每个项目有独立监测开关 3. 定时任务每小时遍历所有开启监测的项目,分别调用 Billing API 查询消费,累加得出总消费 4. 总消费达到额度的阶梯百分比(如 50%/80%/90%)时 → 飞书告警 5. 总消费达到 100% → 自动停用整个子账号 + 飞书告警 6. 主账号可随时追加额度(告警状态自动重置)→ 恢复子账号 **多项目管理:** - 每个子账号可关联 N 个火山项目 - 每个项目有独立的监测开关(开/关) - 可通过"全选"一键开启/关闭所有项目的监测 - 消费明细可按项目展开查看,但告警和停用看的是**所有开启项目的消费总和** **告警状态管理:** - 每个阶梯只通知一次,通过 `triggered_alerts` 字段(存数据库)去重 - 追加额度时自动重置 `triggered_alerts`,按新使用率重新计算 - 不需要月度重置,因为是额度制而非月度制 #### Step 6:AirGate 已实现的 API 接口 ``` # 仪表盘 GET /api/v1/dashboard/ # 总览(用户数/消费/告警) # 火山主账号管理 GET /api/v1/volc-accounts/ # 列出主账号 POST /api/v1/volc-accounts/ # 添加主账号(AK/SK 加密存储) PUT /api/v1/volc-accounts/{id}/ # 更新主账号 DELETE /api/v1/volc-accounts/{id}/ # 删除主账号 POST /api/v1/volc-accounts/{id}/test/ # 测试密钥有效性 # IAM 子账号管理 GET /api/v1/iam-users/ # 列出所有子账号 POST /api/v1/iam-users/sync/ # 从火山同步全部子账号 POST /api/v1/iam-users/import/ # 导入指定子账号 GET /api/v1/iam-users/{id}/ # 查询子账号详情 PUT /api/v1/iam-users/{id}/update/ # 更新配置(告警阈值/开关) POST /api/v1/iam-users/{id}/disable/ # 停用子账号 POST /api/v1/iam-users/{id}/enable/ # 恢复子账号 GET /api/v1/iam-users/{id}/policies/ # 查看权限策略 POST /api/v1/iam-users/{id}/policies/attach/ # 附加权限策略 POST /api/v1/iam-users/{id}/policies/detach/ # 移除权限策略 # 子账号项目管理(多项目关联) GET /api/v1/iam-users/{id}/projects/ # 查看子账号关联的项目列表 POST /api/v1/iam-users/{id}/projects/ # 添加关联项目 PUT /api/v1/iam-users/{id}/projects/{pid}/ # 更新项目监测开关 DELETE /api/v1/iam-users/{id}/projects/{pid}/ # 移除关联项目 POST /api/v1/iam-users/{id}/projects/toggle-all/ # 全选/全不选监测开关 # 额度管理 POST /api/v1/iam-users/{id}/allocate/ # 追加额度(正数)或扣减额度(负数) GET /api/v1/iam-users/{id}/quota-history/ # 查看额度变更记录(含追加和扣减) # 消费查询 GET /api/v1/billing/overview/ # 消费总览 POST /api/v1/billing/refresh/ # 手动刷新消费数据 GET /api/v1/billing/balance/ # 主账号余额 # 全局配置 GET /api/v1/config/ # 查看全局配置 PUT /api/v1/config/ # 更新全局配置 # 告警记录 GET /api/v1/alerts/ # 告警历史(支持类型筛选) # 项目列表 GET /api/v1/projects/ # 从火山拉取项目列表 ``` --- ## 12. 限制与注意事项 ### 12.1 关键限制 | 限制项 | 说明 | |--------|------| | IAM 子账号无独立计费 | 所有费用归主账号,通过多项目聚合追踪(子账号关联 N 个项目,消费=开启监测的项目之和) | | Billing API 无实时数据 | 最快 T+1 天粒度,有 1-2 天延迟 | | 每用户最多 2 个 API 密钥 | 无法创建更多 | | SecretKey 仅返回一次 | 创建后立即保存 | | Billing API QPS 限制 5 | 批量查询需注意限流 | | Ark 推理限额无公开 API | 目前仅支持控制台操作 | | 火山原生预算告警仅通知不自动执行 | AirGate 已自建额度划拨+阶梯告警+自动停用 | ### 12.2 安全建议 1. **主账号 AK/SK 务必安全存储**,建议使用环境变量或密钥管理服务 2. **定期轮换 API 密钥**,利用 `GetAccessKeyLastUsed` 检查不活跃的密钥 3. **遵循最小权限原则**,只授予必要的权限 4. **显式 Deny 策略优先**,防止权限漏洞 5. **监控日志**,使用 CloudTrail 审计 API 调用 ### 12.3 消费监控的精确度问题 由于账单数据有 1-2 天延迟,消费监控存在滞后。AirGate 的应对策略: - **额度划拨制**:划拨的额度应预留 1-2 天延迟的消费余量(如实际想控制 10 万,可划拨 9 万并设阈值 [50, 80, 90]) - **阶梯式告警**:在额度用尽前的多个节点提前告警,给管理员反应时间 - **高频轮询**:每小时查一次,虽然数据本身有延迟,但能在数据更新后第一时间触发告警 - 结合 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 | --- > **当前进度**:AirGate 管控工具已完成核心功能开发(Django 4.2 + DRF + Vue 3 + Element Plus), > 包括 IAM 子账号管理、额度划拨、阶梯式告警、消费监控、飞书通知。 > 项目仓库:https://gitea.airlabs.art/seaislee/AirGate.git > > **待完成**:创建子账号功能、权限策略配置界面、Docker/K8s 部署配置、飞书联调、AirDrama API 对接。