Backend (Django 4.2 + DRF): - Admin auth with SimpleJWT - Volcengine API client with HMAC-SHA256 signing - IAM user management (create/sync/import/disable/enable) - Billing query with pagination - Feishu webhook notifications (async) - APScheduler for periodic spending checks - AES-256 encrypted credential storage - API key auth for external system integration Frontend (Vue 3 + Element Plus): - Login page - Dashboard with stats overview - IAM user list with per-user threshold config - Billing view with spending progress bars - Alert history with type filtering - Settings page for global config and Volcengine account management Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
42 KiB
火山引擎 IAM 子账号管控工具 -- 深度研究报告
研究日期:2026-03-19 目标:通过火山引擎 Open API,实现对 IAM 子账号的全面管控,包括权限隔离、消费监控、告警、自动停用等功能。
目录
- 整体架构方案
- API 认证与签名机制
- IAM 用户管理 API
- 权限策略管理
- API 密钥管理
- 计费与消费查询 API
- 预算与告警机制
- 子账号自动停用/恢复方案
- 项目管理与资源隔离
- SDK 与工具链
- 可执行实施方案
- 限制与注意事项
- 参考文档
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 签名实现
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 策略文档格式
{
"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 密钥。
{
"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 限定到指定存储桶
{
"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 访问
# 停用密钥 = 立即切断子账号的所有 API 调用能力
iam_client.call("UpdateAccessKey", {
"AccessKeyId": "AKLT****",
"Status": "inactive",
"UserName": "sub_user_1"
})
恢复子账号的 API 访问
# 恢复密钥 = 一键恢复
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 账户余额查询
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 |
创建通知组 |
支持的通知渠道:
- 站内信(默认开启)
- 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 完全停用子账号(保留账号,可恢复)
需要同时执行两个操作才能完全停用:
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 一键恢复子账号
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 项目级权限授权
# 在项目范围内授权(子账号只能访问该项目下的资源)
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 使用示例
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 工具
# 安装 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:创建子账号
# 创建 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),或者如果不需要项目隔离则可以全局附加。
# 方案 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 策略
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:创建项目并分配
# 使用项目管理 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:消费查询脚本
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:消费监控与告警服务
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 安全建议
- 主账号 AK/SK 务必安全存储,建议使用环境变量或密钥管理服务
- 定期轮换 API 密钥,利用
GetAccessKeyLastUsed检查不活跃的密钥 - 遵循最小权限原则,只授予必要的权限
- 显式 Deny 策略优先,防止权限漏洞
- 监控日志,使用 CloudTrail 审计 API 调用
12.3 消费监控的精确度问题
由于账单数据有 1-2 天延迟,消费监控存在滞后。应对策略:
- 设置更保守的阈值(如实际想控制 5000 元,告警阈值设 3000,停用阈值设 4000)
- 结合 Ark 推理限额功能(自动暂停,无延迟)
- 高频轮询(如每小时)以尽早发现异常
13. 参考文档
官方文档
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 管理后台。