AirGate/火山引擎IAM子账号管控工具_深度研究报告.md
seaislee1209 a7e030dc57 feat: auto-authorize policies when adding projects to sub-accounts
- Disable now removes all policies (saved to DB) + Enable restores them
- Project add: policies are now user-selected (checkbox), not auto-attached
- Fix serializer allow_blank for optional fields (email/phone/password)
- Better error reporting for policy detach/attach failures
- Handle duplicate user creation with clear error message

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 15:01:18 +08:00

44 KiB
Raw Blame History

火山引擎 IAM 子账号管控工具 -- 深度研究报告

研究日期2026-03-19 目标:通过火山引擎 Open API实现对 IAM 子账号的全面管控,包括权限隔离、消费监控、告警、自动停用等功能。


目录

  1. 整体架构方案
  2. API 认证与签名机制
  3. IAM 用户管理 API
  4. 权限策略管理
  5. API 密钥管理
  6. 计费与消费查询 API
  7. 预算与告警机制
  8. 子账号自动停用/恢复方案
  9. 项目管理与资源隔离
  10. SDK 与工具链
  11. 可执行实施方案
  12. 限制与注意事项
  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 签名实现

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:CreateAccessKeyiam:UpdateAccessKeyiam:DeleteAccessKeyiam:ListAccessKeysiam: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:CreateAccessKeyiam:UpdateAccessKeyiam:DeleteAccessKeyiam: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 查询消费。

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 账户余额查询

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 预算管理 APIBilling 模块)

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
  • 企业微信
  • 自定义 WebhookHTTP POST 回调)

7.3 Ark 推理限额Seedance 专属)

火山方舟Ark平台有推理限额功能:

  • 可设置每个模型的最大 Token 消耗量
  • 达到限额后服务自动暂停
  • 最小调整间隔 2 小时
  • 仅支持在线推理(不含批量)
  • 目前仅支持通过控制台设置,暂无公开 API

7.4 AirGate 自建方案:额度划拨制 + 阶梯式告警

由于火山原生的预算告警仅支持站内信/邮件/短信通知不支持自动停用。AirGate 采用额度划拨制自建:

主账号通过 AirGate 给子账号划拨额度(如 10 万元)
         │
         ▼  定时任务每小时查询 Billing API
   遍历子账号下所有开启监测的项目 → 分别查询消费 → 累加得出总消费
         │
         ▼  总消费对比已划拨额度
         │
         ├── 消费达到额度 50% → 飞书告警
         ├── 消费达到额度 80% → 飞书告警
         ├── 消费达到额度 90% → 飞书告警
         └── 消费达到额度 100% → 自动停用子账号 + 飞书告警

额度用完 → 主账号在 AirGate 追加额度 → 告警状态自动重置 → 恢复子账号
额度给多了 → 主账号在 AirGate 扣减额度 → 告警状态自动重置

关键设计:

  • 多项目聚合:一个子账号可关联多个火山项目,每个项目有独立监测开关。消费 = 所有开启监测的项目消费之和
  • 项目即权限:添加项目时自动调用 AttachPolicyInProject 在项目范围内授权,移除项目时自动回收权限。子账号只能操作被授权的项目,碰不到其他人的资源。添加项目时授权哪些策略由管理员在弹窗中手动选择(从下拉列表选,支持多选,默认不预选任何策略),避免系统自动附加不需要的权限
  • 项目明细可查:前端可展开查看每个项目的独立消费,便于分析哪个团队/项目花得多
  • 非月度制:额度不按月重置,是一次性划拨,用完再充
  • 可追加可扣减:主账号可随时追加额度(+5万或扣减额度-3万支持灵活调整
  • 扣减保护:扣减后总额度不能低于已消费金额(否则会立即触发停用)
  • 阶梯式告警:每个子账号可自定义告警百分比(如 [50, 80, 90]),每档只通知一次
  • 额度变更即重置告警:追加或扣减额度后,已触发的告警状态自动清空,按新的使用率重新计算
  • 累计消费:跨月累计,通过 Billing API 各月数据求和得出
  • 操作留痕:每次划拨/扣减都记录操作人、金额、备注,可追溯

8. 子账号自动停用/恢复方案

8.1 完全停用子账号(保留账号,可恢复)

需要同时执行三个操作才能完全停用:

重要发现2026-03-20 实测验证):仅停用控制台登录 + 停用 API 密钥不够。如果子账号已经登录了火山控制台(浏览器会话未过期),他仍然可以继续操作(如在体验中心生成视频)。必须同时移除所有权限策略,这样即使会话未过期,刷新页面后任何操作都会返回"权限不足"。

def disable_sub_user(iam_client, username: str, access_key_ids: list = None):
    """完全停用子账号(保留账号,可一键恢复)

    三步停用:
    1. 停用控制台登录(阻止新登录)
    2. 停用所有 API 密钥(阻止 API 调用)
    3. 移除所有权限策略(已登录的会话也无法操作)

    移除的策略列表保存到本地数据库,恢复时自动加回。
    """

    # 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
        })

    # 3. 移除所有权限策略(保存到 DB 以便恢复)
    saved_policies = []
    resp = iam_client.call("ListAttachedUserPolicies", {"UserName": username})
    policies = resp.get("Result", {}).get("AttachedPolicyMetadata", [])
    for p in policies:
        policy_name = p["PolicyName"]
        policy_type = p["PolicyType"]
        iam_client.call("DetachUserPolicy", {
            "UserName": username,
            "PolicyName": policy_name,
            "PolicyType": policy_type,
        })
        saved_policies.append({"name": policy_name, "type": policy_type})

    # saved_policies 存入本地 DB 的 IAMUser.saved_policies_on_disable 字段JSONField
    # 恢复时从此字段读取并重新附加

    print(f"用户 {username} 已完全停用(控制台 + {len(access_key_ids)} 个密钥 + {len(saved_policies)} 个策略已移除)")

8.2 一键恢复子账号

def enable_sub_user(iam_client, username: str, access_key_ids: list = None,
                    saved_policies: list = None):
    """一键恢复子账号

    三步恢复(与停用操作完全对称):
    1. 恢复控制台登录
    2. 恢复所有 API 密钥
    3. 重新附加停用时保存的权限策略
    """

    # 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
        })

    # 3. 重新附加停用时保存的策略
    if saved_policies:
        for p in saved_policies:
            iam_client.call("AttachUserPolicy", {
                "UserName": username,
                "PolicyName": p["name"],
                "PolicyType": p["type"],
            })

    # 恢复后清空 saved_policies_on_disable 字段

    print(f"用户 {username} 已恢复(控制台 + {len(access_key_ids)} 个密钥 + {len(saved_policies or [])} 个策略已恢复)")

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与 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消费查询脚本

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.pybackend/apps/monitor/views.py

AirGate 实现的核心流程:

  1. 主账号通过界面给子账号划拨额度(如 10 万元)
  2. 子账号下挂多个火山项目,每个项目有独立监测开关
  3. 定时任务每小时遍历所有开启监测的项目,分别调用 Billing API 查询消费,累加得出总消费
  4. 总消费达到额度的阶梯百分比(如 50%/80%/90%)时 → 飞书告警
  5. 总消费达到 100% → 自动停用整个子账号 + 飞书告警
  6. 主账号可随时追加额度(告警状态自动重置)→ 恢复子账号

多项目管理:

  • 每个子账号可关联 N 个火山项目
  • 每个项目有独立的监测开关(开/关)
  • 可通过"全选"一键开启/关闭所有项目的监测
  • 消费明细可按项目展开查看,但告警和停用看的是所有开启项目的消费总和

告警状态管理:

  • 每个阶梯只通知一次,通过 triggered_alerts 字段(存数据库)去重
  • 追加额度时自动重置 triggered_alerts,按新使用率重新计算
  • 不需要月度重置,因为是额度制而非月度制

Step 6AirGate 已实现的 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/add/       # 添加关联项目(自动在项目范围内授权默认策略)
PUT    /api/v1/iam-users/{id}/projects/{pid}/     # 更新项目监测开关
DELETE /api/v1/iam-users/{id}/projects/{pid}/delete/ # 移除关联项目(自动回收项目范围内的策略)
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 对接。