diff --git a/backend/utils/volcengine_client.py b/backend/utils/volcengine_client.py index fe52120..fbfdadf 100644 --- a/backend/utils/volcengine_client.py +++ b/backend/utils/volcengine_client.py @@ -46,7 +46,7 @@ class VolcengineClient: 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: + def call(self, action: str, params: dict = None, body: str = "", extra_headers: dict = None) -> dict: params = params or {} now = datetime.datetime.now(datetime.timezone.utc) x_date = now.strftime("%Y%m%dT%H%M%SZ") @@ -91,6 +91,9 @@ class VolcengineClient: ), } + if extra_headers: + headers.update(extra_headers) + url = f"https://{self.host}/?{query_string}" try: r = requests.get(url, headers=headers, timeout=30) diff --git a/火山引擎IAM子账号管控工具_深度研究报告.md b/火山引擎IAM子账号管控工具_深度研究报告.md index b9a0586..2ac24e1 100644 --- a/火山引擎IAM子账号管控工具_深度研究报告.md +++ b/火山引擎IAM子账号管控工具_深度研究报告.md @@ -1,7 +1,7 @@ # 火山引擎 IAM 子账号管控工具 -- 深度研究报告 -> 研究日期:2026-03-19 -> 目标:通过火山引擎 Open API,实现对 IAM 子账号的全面管控,包括权限隔离、消费监控、告警、自动停用等功能。 +> 研究日期:2026-03-19(最后更新:2026-03-20) +> 目标:通过火山引擎 Open API,构建 IAM 子账号的完整管控平台。子账号**不登录火山控制台**,所有操作(API Key 管理、消费查询等)均通过 AirGate 完成,实现权限隔离、消费监控、告警、自动停用等功能。 --- @@ -18,8 +18,10 @@ 9. [项目管理与资源隔离](#9-项目管理与资源隔离) 10. [SDK 与工具链](#10-sdk-与工具链) 11. [可执行实施方案](#11-可执行实施方案) -12. [限制与注意事项](#12-限制与注意事项) -13. [参考文档](#13-参考文档) +12. [方舟 API Key 管理](#12-方舟-api-key-管理) +13. [实测发现与架构决策](#13-实测发现与架构决策) +14. [限制与注意事项](#14-限制与注意事项) +15. [参考文档](#15-参考文档) --- @@ -29,40 +31,54 @@ | 需求 | 实现方式 | 可行性 | |------|----------|--------| -| 子账号不能看到主账号信息 | IAM 默认零权限 + 显式 Deny 策略 | **完全可行** | -| 子账号仅有 Seedance 2.0 + TOS 权限 | 仅附加 ArkFullAccess + TOSFullAccess 策略 | **完全可行** | +| 子账号不能看到主账号信息 | **子账号不登录火山控制台**,只登录 AirGate | **完全可行** | +| 子账号仅有 Seedance 2.0 + TOS 权限 | 项目级附加 ArkFullAccess + TOSFullAccess,全局无权限 | **完全可行** | | 子账号能看到自己的账单 | 通过 AirGate 按多项目聚合查询,主账号代查展示,可按项目查看明细 | **完全可行** | -| 子账号不能看到其他账号消费/余额 | 不授予 billing/bss 权限 + 显式 Deny | **完全可行** | +| 子账号不能看到其他账号消费/余额 | AirGate 只展示自己的数据,子账号进不了火山后台 | **完全可行** | +| 子账号能管理自己的 API Key | AirGate 调用方舟 API(POST + JSON body)代为管理 | **完全可行**(已验证) | | 消费达到阈值发告警 | 额度划拨制 + 阶梯式告警(50%/80%/90%)+ 飞书通知 | **完全可行** | -| 消费达到阈值自动停用 | 消费达到已划拨额度 100% 时自动停用 | **完全可行** | -| 一键恢复子账号 | 调用 IAM API 重新启用 | **完全可行** | +| 消费达到阈值自动停用 | 消费达到已划拨额度 100% 时自动停用(停登录+停密钥+移除策略) | **完全可行** | +| 一键恢复子账号 | 调用 IAM API 恢复登录+密钥+策略(从快照恢复) | **完全可行** | ### 1.2 架构图 ``` -┌──────────────────────────────────────────────────────┐ -│ 管控工具 (后端服务) │ -│ │ -│ ┌─────────┐ ┌──────────┐ ┌────────────┐ │ -│ │ IAM管理 │ │ 消费监控 │ │ 告警引擎 │ │ -│ │ 模块 │ │ 模块 │ │ 模块 │ │ -│ └────┬─────┘ └────┬─────┘ └────┬───────┘ │ -│ │ │ │ │ -│ ┌────▼─────────────▼─────────────▼───────┐ │ -│ │ 火山引擎 Open API 调用层 │ │ -│ │ (HMAC-SHA256 签名认证) │ │ -│ └────────────────────────────────────────┘ │ -└──────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────┐ +│ AirGate(子账号的唯一操作入口) │ +│ │ +│ 管理员界面 子账号界面 │ +│ ┌───────────────┐ ┌──────────────┐ │ +│ │ 子账号管理 │ │ 我的 API Key │ │ +│ │ 消费监控/告警 │ │ 我的消费 │ │ +│ │ 额度划拨 │ │ 我的项目 │ │ +│ │ 权限配置 │ └──────────────┘ │ +│ │ 系统管理 │ │ +│ └───────────────┘ │ +│ │ +│ ┌────────────────────────────────────────────────┐ │ +│ │ 火山引擎 Open API 调用层 │ │ +│ │ IAM API | Billing API | 方舟 API (Ark) │ │ +│ │ (HMAC-SHA256 签名认证) │ │ +│ └────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────┘ │ │ │ - ┌────▼────┐ ┌─────▼─────┐ ┌────▼─────┐ - │ IAM API │ │Billing API│ │CloudMonitor│ + ┌────▼────┐ ┌─────▼─────┐ ┌────▼──────┐ + │ IAM API │ │Billing API│ │ 方舟 API │ │iam.vol..│ │billing.vol│ │open.vol.. │ - └─────────┘ └───────────┘ └────────────┘ + └─────────┘ └───────────┘ └───────────┘ ``` -### 1.3 关键发现 +### 1.3 关键发现与架构决策 -> **重要**:火山引擎的 IAM 子用户**没有独立的计费账户**。所有费用归属主账号。子账号的消费追踪需要通过**项目(Project)**或**标签(Tag)**维度来实现,由主账号通过 Billing API 查询后聚合展示。 +> **重要发现(2026-03-20 实测验证)**: +> +> 1. **火山控制台的权限隔离不彻底**:给子账号全局 `ArkReadOnlyAccess` 后,子账号在方舟控制台能看到**所有项目**的 API Key,包括其他子账号和主账号的资源。项目级授权能控制"能不能操作",但控制台页面渲染依赖全局只读权限。 +> +> 2. **方舟 API Key 管理页面需要 `ArkExperienceAccess` 全局权限**:即使给了项目级 `ArkFullAccess`,不加全局体验权限就无法进入 API Key 管理页面。而加了全局权限又会泄露其他项目信息。 +> +> 3. **架构决策**:基于以上发现,AirGate 的定位从"管控工具"升级为"子账号的唯一操作入口"。子账号**不登录火山控制台**,所有操作(创建/查看/删除 API Key、查看消费等)均通过 AirGate 完成。AirGate 使用主账号的 AK/SK 调用火山 API,在应用层做项目级隔离。 +> +> 4. 火山引擎的 IAM 子用户**没有独立的计费账户**。所有费用归属主账号。子账号的消费追踪需要通过**项目(Project)**维度来实现。 --- @@ -868,13 +884,14 @@ iam.call("CreateUser", { "MobilePhone": "+8618000000000" }) -# 开通控制台登录 -iam.call("CreateLoginProfile", { - "UserName": "dept_a_user", - "Password": "Initial@Pass123", - "LoginAllowed": "true", - "PasswordResetRequired": "true" -}) +# 注意:不开通控制台登录(子账号通过 AirGate 操作,不登录火山控制台) +# 如果确实需要开通控制台登录(不推荐),取消以下注释: +# iam.call("CreateLoginProfile", { +# "UserName": "dept_a_user", +# "Password": "Initial@Pass123", +# "LoginAllowed": "true", +# "PasswordResetRequired": "true" +# }) # 创建 API 密钥(记录返回的 SecretAccessKey!) result = iam.call("CreateAccessKey", {"UserName": "dept_a_user"}) @@ -1119,25 +1136,162 @@ GET /api/v1/alerts/ # 告警历史(支持类型筛 # 项目列表 GET /api/v1/projects/ # 从火山拉取项目列表 + +# 方舟 API Key 管理(AirGate 代为操作,子账号只看到自己项目的 Key) +GET /api/v1/ark-keys/{project_name}/ # 列出指定项目下的 API Key +POST /api/v1/ark-keys/{project_name}/create/ # 在指定项目下创建 API Key +POST /api/v1/ark-keys/{key_id}/disable/ # 停用 API Key +POST /api/v1/ark-keys/{key_id}/enable/ # 启用 API Key +DELETE /api/v1/ark-keys/{key_id}/ # 删除 API Key + +# 管理员管理 +GET /api/v1/auth/admins/ # 列出所有管理员 +POST /api/v1/auth/admins/create/ # 创建管理员 +POST /api/v1/auth/admins/{id}/toggle/ # 启用/停用管理员 +POST /api/v1/auth/admins/{id}/reset-password/ # 重置管理员密码 +POST /api/v1/auth/change-password/ # 修改当前用户密码 + +# 子账号项目策略管理 +PUT /api/v1/iam-users/{id}/projects/{pid}/policies/ # 更新项目级授权策略 ``` --- -## 12. 限制与注意事项 +## 12. 方舟 API Key 管理 -### 12.1 关键限制 +### 12.1 接口发现(2026-03-20 实测验证) + +方舟 API Key 管理使用 **POST + JSON body** 方式调用,与 IAM API 的 GET + Query 方式不同。 + +| 参数 | 值 | +|------|-----| +| 端点 | `open.volcengineapi.com` | +| Service | `ark` | +| Version | `2024-01-01` | +| HTTP 方法 | **POST**(必须,GET 不传 body 会报 MissingParameter) | +| Content-Type | `application/json` | +| 签名 | HMAC-SHA256,signed_headers 包含 `content-type;host;x-content-sha256;x-date` | + +### 12.2 已验证的接口 + +```python +# ListApiKeys - 列出项目下的 API Key +POST https://open.volcengineapi.com/?Action=ListApiKeys&Version=2024-01-01 +Body: {"ProjectName": "zyc_test", "PageSize": 10} + +# 返回结果包含: +# - TotalCount: 总数 +# - Items[].Id: Key ID +# - Items[].Key: "fedd****a052"(脱敏) +# - Items[].ProjectName: 所属项目 +# - Items[].Name: Key 名称 +# - Items[].Status: Active/Inactive +# - Items[].Tags[]: 包含创建者信息(如 IAMUser/76804896/zyc) +``` + +### 12.3 待验证的接口 + +以下接口需要实际调用验证参数: + +```python +# CreateApiKey - 创建 API Key +POST ?Action=CreateApiKey&Version=2024-01-01 +Body: {"ProjectName": "xxx", "Name": "key-name", "ResourceInstances": [...]} + +# DeleteApiKey - 删除 API Key +POST ?Action=DeleteApiKey&Version=2024-01-01 +Body: {"ApiKeyId": "xxx"} + +# UpdateApiKey - 更新 API Key(启用/停用) +POST ?Action=UpdateApiKey&Version=2024-01-01 +Body: {"ApiKeyId": "xxx", "Status": "Active/Inactive"} +``` + +### 12.4 AirGate 集成方案 + +AirGate 作为子账号的唯一操作入口,代理方舟 API Key 管理: + +``` +子账号登录 AirGate + → 看到自己关联的项目 + → 选择项目 → 查看该项目下的 API Key(只看自己项目的) + → 创建新 Key / 停用 Key / 删除 Key + → AirGate 后端用主账号 AK/SK 调用方舟 API 执行操作 + → 项目级隔离由 AirGate 应用层控制(查询时只传子账号关联的项目名) +``` + +**关键**:子账号不需要火山控制台的任何权限来管理 API Key,因为所有操作都由 AirGate 使用主账号身份代为执行。 + +--- + +## 13. 实测发现与架构决策 + +### 13.1 火山控制台权限隔离问题(2026-03-20) + +| 测试场景 | 结果 | +|----------|------| +| 项目级 `ArkFullAccess` + 无全局权限 | 无法进入方舟控制台页面,提示需要 `ArkReadOnlyAccess` | +| 全局 `ArkReadOnlyAccess` | 能进入控制台,但能看到**所有项目**的 API Key | +| 全局 `ArkExperienceAccess` | 能进入体验中心,能看到所有项目的内容 | +| 停用账号但子账号未刷新页面 | 子账号仍可在体验中心生成视频 | +| 停用账号 + 移除所有策略 | 子账号刷新页面后立即失效 | + +**结论**:火山控制台无法实现项目级的视图隔离。要实现"子账号只看到自己项目",必须在应用层(AirGate)控制。 + +### 13.2 最终权限方案 + +``` +子账号在火山引擎上的权限(由 AirGate 自动管理): + +全局权限:无(不需要任何全局策略) + 或仅保留 AccessKeySelfManageAccess(如果需要) + +项目级权限(通过 AttachUserPolicy + ProjectName): + ├── ArkFullAccess ← API 层面有完整方舟操作权限 + └── TOSFullAccess ← API 层面有 TOS 操作权限 + +火山控制台登录:不开通(不给密码 / 停用 LoginProfile) +``` + +子账号**不能也不需要**登录火山控制台。所有操作通过 AirGate 完成: + +| 操作 | 在哪里做 | +|------|----------| +| 创建/查看/删除 API Key | AirGate(代调方舟 API) | +| 查看消费 | AirGate(代调 Billing API) | +| 管理项目 | AirGate(管理员操作) | +| 使用 Seedance 2.0 | 直接用 API Key 调用(不需要控制台) | + +### 13.3 停用/恢复增强方案(2026-03-20 实测验证) + +停用操作执行三步(确保即使子账号有活跃浏览器会话也立即失效): + +1. **停用控制台登录**:`UpdateLoginProfile(LoginAllowed=false)` — 阻止新登录 +2. **停用所有 API 密钥**:`UpdateAccessKey(Status=inactive)` — 阻止 API 调用 +3. **移除所有权限策略**:遍历 `ListAttachedUserPolicies` 结果,逐个 `DetachUserPolicy` — 已登录的会话刷新后也无法操作 + +移除的策略列表保存到数据库 `saved_policies_on_disable` 字段(JSONField),恢复时自动附加回来。 + +--- + +## 14. 限制与注意事项 + +### 14.1 关键限制 | 限制项 | 说明 | |--------|------| | IAM 子账号无独立计费 | 所有费用归主账号,通过多项目聚合追踪(子账号关联 N 个项目,消费=开启监测的项目之和) | | Billing API 无实时数据 | 最快 T+1 天粒度,有 1-2 天延迟 | -| 每用户最多 2 个 API 密钥 | 无法创建更多 | -| SecretKey 仅返回一次 | 创建后立即保存 | +| 每用户最多 2 个 IAM AccessKey | IAM 级别的 AK/SK 最多 2 对(方舟 API Key 无此限制) | +| IAM SecretKey 仅返回一次 | 创建后立即保存 | | Billing API QPS 限制 5 | 批量查询需注意限流 | -| Ark 推理限额无公开 API | 目前仅支持控制台操作 | +| **火山控制台无法做项目级视图隔离** | 全局只读权限会暴露所有项目的资源(实测验证),所以子账号不登录火山控制台 | +| **方舟 API Key 管理需全局权限** | 控制台 API Key 页面需要 `ArkExperienceAccess` 全局权限,无法限定项目范围 | +| 停用账号不会踢掉已登录会话 | 需要同时移除策略,子账号刷新页面后才失效 | | 火山原生预算告警仅通知不自动执行 | AirGate 已自建额度划拨+阶梯告警+自动停用 | +| 方舟 API 使用 POST + JSON body | 与 IAM/Billing 的 GET + Query 方式不同,签名方式也不同 | -### 12.2 安全建议 +### 14.2 安全建议 1. **主账号 AK/SK 务必安全存储**,建议使用环境变量或密钥管理服务 2. **定期轮换 API 密钥**,利用 `GetAccessKeyLastUsed` 检查不活跃的密钥 @@ -1145,7 +1299,7 @@ GET /api/v1/projects/ # 从火山拉取项目列表 4. **显式 Deny 策略优先**,防止权限漏洞 5. **监控日志**,使用 CloudTrail 审计 API 调用 -### 12.3 消费监控的精确度问题 +### 14.3 消费监控的精确度问题 由于账单数据有 1-2 天延迟,消费监控存在滞后。AirGate 的应对策略: - **额度划拨制**:划拨的额度应预留 1-2 天延迟的消费余量(如实际想控制 10 万,可划拨 9 万并设阈值 [50, 80, 90]) @@ -1155,7 +1309,7 @@ GET /api/v1/projects/ # 从火山拉取项目列表 --- -## 13. 参考文档 +## 15. 参考文档 ### 官方文档