docs: major report update - AirGate as sole entry point for sub-accounts

Key changes:
- Architecture upgraded: sub-accounts do NOT log into Volcengine console
- Documented Ark API Key management via POST+JSON (verified working)
- Added chapter 12 (Ark API Key mgmt) and 13 (实测发现 with decisions)
- Fixed Step 1 code example to NOT create console login
- Updated core requirements table, architecture diagram, limitations
- All findings verified through actual API testing on 2026-03-20

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
seaislee1209 2026-03-20 20:36:07 +08:00
parent 314612f454
commit 8e564ed640
2 changed files with 200 additions and 43 deletions

View File

@ -46,7 +46,7 @@ class VolcengineClient:
def _hash_sha256(self, content: str) -> str: def _hash_sha256(self, content: str) -> str:
return hashlib.sha256(content.encode("utf-8")).hexdigest() 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 {} params = params or {}
now = datetime.datetime.now(datetime.timezone.utc) now = datetime.datetime.now(datetime.timezone.utc)
x_date = now.strftime("%Y%m%dT%H%M%SZ") 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}" url = f"https://{self.host}/?{query_string}"
try: try:
r = requests.get(url, headers=headers, timeout=30) r = requests.get(url, headers=headers, timeout=30)

View File

@ -1,7 +1,7 @@
# 火山引擎 IAM 子账号管控工具 -- 深度研究报告 # 火山引擎 IAM 子账号管控工具 -- 深度研究报告
> 研究日期2026-03-19 > 研究日期2026-03-19最后更新2026-03-20
> 目标:通过火山引擎 Open API实现对 IAM 子账号的全面管控,包括权限隔离、消费监控、告警、自动停用等功能。 > 目标:通过火山引擎 Open API构建 IAM 子账号的完整管控平台。子账号**不登录火山控制台**所有操作API Key 管理、消费查询等)均通过 AirGate 完成,实现权限隔离、消费监控、告警、自动停用等功能。
--- ---
@ -18,8 +18,10 @@
9. [项目管理与资源隔离](#9-项目管理与资源隔离) 9. [项目管理与资源隔离](#9-项目管理与资源隔离)
10. [SDK 与工具链](#10-sdk-与工具链) 10. [SDK 与工具链](#10-sdk-与工具链)
11. [可执行实施方案](#11-可执行实施方案) 11. [可执行实施方案](#11-可执行实施方案)
12. [限制与注意事项](#12-限制与注意事项) 12. [方舟 API Key 管理](#12-方舟-api-key-管理)
13. [参考文档](#13-参考文档) 13. [实测发现与架构决策](#13-实测发现与架构决策)
14. [限制与注意事项](#14-限制与注意事项)
15. [参考文档](#15-参考文档)
--- ---
@ -29,40 +31,54 @@
| 需求 | 实现方式 | 可行性 | | 需求 | 实现方式 | 可行性 |
|------|----------|--------| |------|----------|--------|
| 子账号不能看到主账号信息 | IAM 默认零权限 + 显式 Deny 策略 | **完全可行** | | 子账号不能看到主账号信息 | **子账号不登录火山控制台**,只登录 AirGate | **完全可行** |
| 子账号仅有 Seedance 2.0 + TOS 权限 | 仅附加 ArkFullAccess + TOSFullAccess 策略 | **完全可行** | | 子账号仅有 Seedance 2.0 + TOS 权限 | 项目级附加 ArkFullAccess + TOSFullAccess全局无权限 | **完全可行** |
| 子账号能看到自己的账单 | 通过 AirGate 按多项目聚合查询,主账号代查展示,可按项目查看明细 | **完全可行** | | 子账号能看到自己的账单 | 通过 AirGate 按多项目聚合查询,主账号代查展示,可按项目查看明细 | **完全可行** |
| 子账号不能看到其他账号消费/余额 | 不授予 billing/bss 权限 + 显式 Deny | **完全可行** | | 子账号不能看到其他账号消费/余额 | AirGate 只展示自己的数据,子账号进不了火山后台 | **完全可行** |
| 子账号能管理自己的 API Key | AirGate 调用方舟 APIPOST + JSON body代为管理 | **完全可行**(已验证) |
| 消费达到阈值发告警 | 额度划拨制 + 阶梯式告警50%/80%/90%+ 飞书通知 | **完全可行** | | 消费达到阈值发告警 | 额度划拨制 + 阶梯式告警50%/80%/90%+ 飞书通知 | **完全可行** |
| 消费达到阈值自动停用 | 消费达到已划拨额度 100% 时自动停用 | **完全可行** | | 消费达到阈值自动停用 | 消费达到已划拨额度 100% 时自动停用(停登录+停密钥+移除策略) | **完全可行** |
| 一键恢复子账号 | 调用 IAM API 重新启用 | **完全可行** | | 一键恢复子账号 | 调用 IAM API 恢复登录+密钥+策略(从快照恢复) | **完全可行** |
### 1.2 架构图 ### 1.2 架构图
``` ```
┌──────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────┐
│ 管控工具 (后端服务) │ │ AirGate子账号的唯一操作入口
│ │ │ │
│ ┌─────────┐ ┌──────────┐ ┌────────────┐ │ │ 管理员界面 子账号界面 │
│ │ IAM管理 │ │ 消费监控 │ │ 告警引擎 │ │ │ ┌───────────────┐ ┌──────────────┐ │
│ │ 模块 │ │ 模块 │ │ 模块 │ │ │ │ 子账号管理 │ │ 我的 API Key │ │
│ └────┬─────┘ └────┬─────┘ └────┬───────┘ │ │ │ 消费监控/告警 │ │ 我的消费 │ │
│ │ │ │ │ │ │ 额度划拨 │ │ 我的项目 │ │
│ ┌────▼─────────────▼─────────────▼───────┐ │ │ │ 权限配置 │ └──────────────┘ │
│ │ 火山引擎 Open API 调用层 │ │ │ │ 系统管理 │ │
│ │ (HMAC-SHA256 签名认证) │ │ │ └───────────────┘ │
│ └────────────────────────────────────────┘ │ │ │
└──────────────────────────────────────────────────────┘ │ ┌────────────────────────────────────────────────┐ │
│ │ 火山引擎 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.. │ │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" "MobilePhone": "+8618000000000"
}) })
# 开通控制台登录 # 注意:不开通控制台登录(子账号通过 AirGate 操作,不登录火山控制台)
iam.call("CreateLoginProfile", { # 如果确实需要开通控制台登录(不推荐),取消以下注释:
"UserName": "dept_a_user", # iam.call("CreateLoginProfile", {
"Password": "Initial@Pass123", # "UserName": "dept_a_user",
"LoginAllowed": "true", # "Password": "Initial@Pass123",
"PasswordResetRequired": "true" # "LoginAllowed": "true",
}) # "PasswordResetRequired": "true"
# })
# 创建 API 密钥(记录返回的 SecretAccessKey # 创建 API 密钥(记录返回的 SecretAccessKey
result = iam.call("CreateAccessKey", {"UserName": "dept_a_user"}) result = iam.call("CreateAccessKey", {"UserName": "dept_a_user"})
@ -1119,25 +1136,162 @@ GET /api/v1/alerts/ # 告警历史(支持类型筛
# 项目列表 # 项目列表
GET /api/v1/projects/ # 从火山拉取项目列表 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-SHA256signed_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 个项目,消费=开启监测的项目之和) | | IAM 子账号无独立计费 | 所有费用归主账号,通过多项目聚合追踪(子账号关联 N 个项目,消费=开启监测的项目之和) |
| Billing API 无实时数据 | 最快 T+1 天粒度,有 1-2 天延迟 | | Billing API 无实时数据 | 最快 T+1 天粒度,有 1-2 天延迟 |
| 每用户最多 2 个 API 密钥 | 无法创建更多 | | 每用户最多 2 个 IAM AccessKey | IAM 级别的 AK/SK 最多 2 对(方舟 API Key 无此限制) |
| SecretKey 仅返回一次 | 创建后立即保存 | | IAM SecretKey 仅返回一次 | 创建后立即保存 |
| Billing API QPS 限制 5 | 批量查询需注意限流 | | Billing API QPS 限制 5 | 批量查询需注意限流 |
| Ark 推理限额无公开 API | 目前仅支持控制台操作 | | **火山控制台无法做项目级视图隔离** | 全局只读权限会暴露所有项目的资源(实测验证),所以子账号不登录火山控制台 |
| **方舟 API Key 管理需全局权限** | 控制台 API Key 页面需要 `ArkExperienceAccess` 全局权限,无法限定项目范围 |
| 停用账号不会踢掉已登录会话 | 需要同时移除策略,子账号刷新页面后才失效 |
| 火山原生预算告警仅通知不自动执行 | AirGate 已自建额度划拨+阶梯告警+自动停用 | | 火山原生预算告警仅通知不自动执行 | AirGate 已自建额度划拨+阶梯告警+自动停用 |
| 方舟 API 使用 POST + JSON body | 与 IAM/Billing 的 GET + Query 方式不同,签名方式也不同 |
### 12.2 安全建议 ### 14.2 安全建议
1. **主账号 AK/SK 务必安全存储**,建议使用环境变量或密钥管理服务 1. **主账号 AK/SK 务必安全存储**,建议使用环境变量或密钥管理服务
2. **定期轮换 API 密钥**,利用 `GetAccessKeyLastUsed` 检查不活跃的密钥 2. **定期轮换 API 密钥**,利用 `GetAccessKeyLastUsed` 检查不活跃的密钥
@ -1145,7 +1299,7 @@ GET /api/v1/projects/ # 从火山拉取项目列表
4. **显式 Deny 策略优先**,防止权限漏洞 4. **显式 Deny 策略优先**,防止权限漏洞
5. **监控日志**,使用 CloudTrail 审计 API 调用 5. **监控日志**,使用 CloudTrail 审计 API 调用
### 12.3 消费监控的精确度问题 ### 14.3 消费监控的精确度问题
由于账单数据有 1-2 天延迟消费监控存在滞后。AirGate 的应对策略: 由于账单数据有 1-2 天延迟消费监控存在滞后。AirGate 的应对策略:
- **额度划拨制**:划拨的额度应预留 1-2 天延迟的消费余量(如实际想控制 10 万,可划拨 9 万并设阈值 [50, 80, 90] - **额度划拨制**:划拨的额度应预留 1-2 天延迟的消费余量(如实际想控制 10 万,可划拨 9 万并设阈值 [50, 80, 90]
@ -1155,7 +1309,7 @@ GET /api/v1/projects/ # 从火山拉取项目列表
--- ---
## 13. 参考文档 ## 15. 参考文档
### 官方文档 ### 官方文档