多租户管理

更新时间:
复制为 MD 格式

多个租户共享同一 Agent Sandbox 集群时,每个租户通过 Team 映射到独立的 Kubernetes Namespace,并持有各自的 API Key 作为访问凭证。管理员和租户可通过 HTTP 接口创建、查询和吊销 API Key。

概述

ack-sandbox-manager 提供一组兼容 E2B 协议的 HTTP 接口,用于管理 API Key 与 Team。集群管理员与租户可通过这些接口以编程方式创建 API Key、查询所属 Team,并吊销不再使用的 Key。

这些接口没有配套的 E2B SDK,也没有对应的 Kubernetes CRD,必须直接通过 HTTP 协议调用。

Team

Team 是 API Key 的授权边界。在 Agent Sandbox 中,Team 的身份由 Team 名称唯一标识,且 Team 名称直接映射到一个 Kubernetes Namespace。

  • 内置的 admin Team 是集群级的,由管理员管理,其持有的 admin API Key 在 ack-sandbox-manager 启动时一同初始化。

  • 其他任何 Team 名称都必须对应一个已经存在的 Kubernetes Namespace。当租户为 Team foo 创建 API Key 时,Namespace foo 必须预先存在,否则请求会返回错误。

Namespace 的唯一性已经提供了足够的隔离边界,因此 Team的唯一标识符(UUID)仅作为展示用元数据保留,不参与鉴权和资源查找。

API Key

API Key 是某个 Team 长期持有的访问凭证。客户端在调用 ack-sandbox-manager 任意接口时,都必须通过 X-API-KEY 请求头携带它。API Key 决定了:

  • 调用方属于哪个 Team。

  • 调用方是普通租户还是集群管理员(即是否属于 admin Team)。

  • 调用方可访问的 Sandbox 范围,普通租户只能访问由自身 API Key 创建的 Sandbox,管理员可以访问全部 Sandbox。

授权模型

角色

列出当前Team 的 Key

列出 Teams

为当前Team 创建 Key

为其他 Team 创建 Key

删除当前Team 的 Key

删除 admin API Key

管理员(admin Team)

支持

支持(全部 Team)

支持

支持

支持

不支持(被拒绝)

普通租户

支持

支持(仅自身所在 Team)

支持

不支持

支持

不支持

重要

内置的 admin API Key 在任何情况下都不能被删除,以保护集群的可控性。

准备工作

  • 前往目标集群组件管理,确认已安装ack-sandbox-manager且已升级至 v0.6.0 及以上版本。并在组件配置页面进行如下配置:

    • 勾选Whether to enable E2B_API_KEY authentication,启用API Key鉴权功能。

    • 配置adminApiKey(管理员API Key)。

端到端流程示例

  1. 获取集群KubeConfig并通过kubectl工具连接集群

  2. 安装JSON处理器jq

  3. 以下脚本完整演示了从管理员初始化资源环境与租户凭证,到租户自主实现团队密钥全生命周期管理的端到端业务流程。

    #!/usr/bin/env bash
    set -euo pipefail
    
    ADMIN_KEY="${ADMIN_KEY:?required}"
    # 支持两种协议,示例使用原生E2B协议地址 https://api.your.domain.com
    # 如需使用Agent Sandbox私有协议,将BASE_URL替换为 https://your.domain.com/kruise/api。
    BASE_URL="${BASE_URL:-https://api.your.domain.com}"
    TEAM_NAMESPACE="team-a"
    
    # 1. 管理员在集群中创建 Team 对应的 Namespace
    kubectl create ns "${TEAM_NAMESPACE}"
    
    # 2. 管理员为 team-a 创建租户 Key,记录返回的明文 key
    TENANT_KEY=$(curl -fsS -X POST \
      -H "X-API-KEY: ${ADMIN_KEY}" \
      -H "Content-Type: application/json" \
      -d "{\"name\":\"ci-runner\",\"teamName\":\"${TEAM_NAMESPACE}\"}" \
      "${BASE_URL}/api-keys" | jq -r '.key')
    
    # 3. 租户列出所属 Team 与现有 Key
    curl -fsS -H "X-API-KEY: ${TENANT_KEY}" "${BASE_URL}/teams"
    curl -fsS -H "X-API-KEY: ${TENANT_KEY}" "${BASE_URL}/api-keys"
    
    # 4. 租户为所属 Team 创建子 Key(无需 teamName 字段)
    SUB_KEY_ID=$(curl -fsS -X POST \
      -H "X-API-KEY: ${TENANT_KEY}" \
      -H "Content-Type: application/json" \
      -d '{"name":"my-sub-key"}' \
      "${BASE_URL}/api-keys" | jq -r '.id')
    
    # 5. 租户删除所属 Team 的子 Key
    curl -fsS -X DELETE \
      -H "X-API-KEY: ${TENANT_KEY}" \
      "${BASE_URL}/api-keys/${SUB_KEY_ID}"

接口参考

Team列表

获取当前用户可见的 Team。普通租户只能看到自身所属的 Team;管理员可以看到所有 Team。

请求示例:

curl -fsS \
  -H "X-API-KEY: ${E2B_API_KEY}" \
  "https://api.your.domain.com/teams"
import os
import requests

resp = requests.get(
    "https://api.your.domain.com/teams",
    headers={"X-API-KEY": os.environ["E2B_API_KEY"]},
    timeout=10,
)
try:
    resp.raise_for_status()
except requests.HTTPError:
    print(resp.text)
    raise
print(resp.json())

预期输出:

[
  {
    "teamID": "550e8400-e29b-41d4-a716-446655449999",
    "name": "admin",
    "apiKey": "",
    "isDefault": true
  }
]

响应体为 JSON 数组,每个元素的字段说明如下表。

字段

类型

说明

teamID

string

Team 的 UUID,仅用于展示。

name

string

Team 名称,与 Kubernetes Namespace 一致。

apiKey

string

该字段始终返回空字符串,仅为兼容 E2B SDK Schema 保留。如需获取 API Key 明文,请使用 POST /api-keys 创建并立即保存返回值。

isDefault

boolean

是否为调用方的默认 Team。

API Key列表

获取当前调用方所属 Team 下的所有 API Key。

请求示例:

curl -fsS \
  -H "X-API-KEY: ${E2B_API_KEY}" \
  "https://api.your.domain.com/api-keys"
import os
import requests

resp = requests.get(
    "https://api.your.domain.com/api-keys",
    headers={"X-API-KEY": os.environ["E2B_API_KEY"]},
    timeout=10,
)
try:
    resp.raise_for_status()
except requests.HTTPError:
    print(resp.text)
    raise
for key in resp.json():
    print(key["id"], key["name"], key["mask"])

响应字段说明:

字段

类型

说明

id

string

API Key 的 UUID,删除时使用。

name

string

Key 的可读名称。

createdAt

string

Key 的创建时间。

createdBy.id

string

创建该 Key 的调用方 UUID。

lastUsed

string

Key 最近一次使用时间,从未使用时为 null

创建 API Key

创建新的 API Key。响应体中的 key 字段为刚刚生成的明文 Key。明文 key 字段仅在创建时返回一次,后续无法再通过任何接口取回。请立即持久化保存。

说明

运行示例前,请确保 team-a 命名空间已经存在。如调用方为普通租户,请删除请求体中的 teamName 字段。

请求示例:

curl -fsS -X POST \
  -H "X-API-KEY: ${E2B_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"name": "ci-runner", "teamName": "team-a"}' \
  "https://api.your.domain.com/api-keys"
import os
import requests

resp = requests.post(
    "https://api.your.domain.com/api-keys",
    headers={
        "X-API-KEY": os.environ["E2B_API_KEY"],
        "Content-Type": "application/json",
    },
    json={"name": "ci-runner", "teamName": "team-a"},
    timeout=10,
)
try:
    resp.raise_for_status()
except requests.HTTPError:
    print(resp.text)
    raise
created = resp.json()
# 请立刻持久化 created["key"] —— 后续无法再次获取。
print(created["id"], created["key"])

请求体字段:

字段

类型

是否必填

说明

name

string

Key 的可读名称。

teamName

string

目标 Team 名称,默认为调用者自身所属的 Team。仅管理员可指向其他 Team,且必须指向一个已经存在的 Kubernetes Namespace。普通租户必须省略此字段。

响应字段:

字段

类型

说明

id

string

新 Key 的 UUID,后续删除时使用。

key

string

明文 Key,仅返回一次,必须立即保存。

name

string

与请求体一致的 Key 名称。

team.id

string

Key 实际归属的 Team UUID。

team.name

string

Key 实际归属的 Team 名称(即对应的 Kubernetes Namespace),用于校验调用方意图。

删除 API Key

通过 UUID 删除指定的 API Key。普通租户只能删除当前Team 下的 Key;管理员可以删除 admin API Key 以外的任意 Key。尝试删除内置 admin API Key 会返回 403 Forbidden

请求示例:

API_KEY_ID="<uuid-from-list-api-keys>"
curl -fsS -X DELETE \
  -H "X-API-KEY: ${E2B_API_KEY}" \
  "https://api.your.domain.com/api-keys/${API_KEY_ID}"
import os
import requests

api_key_id = "<uuid-from-list-api-keys>"
resp = requests.delete(
    f"https://api.your.domain.com/api-keys/{api_key_id}",
    headers={"X-API-KEY": os.environ["E2B_API_KEY"]},
    timeout=10,
)
# 成功时返回 204 No Content。
try:
    resp.raise_for_status()
except requests.HTTPError:
    print(resp.text)
    raise
无效UUID 与不存在的 UUID 一律返回 404 Not Found,接口不区分这两种情况。

错误码

HTTP 状态码

场景

400

请求体不合法(JSON 解析失败)或目标 Namespace 不存在。

401

未携带 X-API-KEY 或 Key 无效。

403

非管理员对其他 Team 进行写操作,或尝试删除 admin API Key。

404

指定 UUID 的 API Key 不存在,或路径参数不是合法 UUID 字符串。

500

服务器内部错误,例如缺少必填字段、Kubernetes API 不可用及持久化存储不可达等。

错误响应体为 JSON,包含 codemessagerequest_id 三个字段,便于定位问题:

{
  "code": 403,
  "message": "You are not allowed to create an API key for another team",
  "request_id": "c42c84d8-8420-4194-b48d-5bc5c8fe4ed6"
}