多个租户共享同一 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。
内置的
adminTeam 是集群级的,由管理员管理,其持有的 admin API Key 在ack-sandbox-manager启动时一同初始化。其他任何 Team 名称都必须对应一个已经存在的 Kubernetes Namespace。当租户为 Team
foo创建 API Key 时,Namespacefoo必须预先存在,否则请求会返回错误。
Namespace 的唯一性已经提供了足够的隔离边界,因此 Team的唯一标识符(UUID)仅作为展示用元数据保留,不参与鉴权和资源查找。
API Key
API Key 是某个 Team 长期持有的访问凭证。客户端在调用 ack-sandbox-manager 任意接口时,都必须通过 X-API-KEY 请求头携带它。API Key 决定了:
调用方属于哪个 Team。
调用方是普通租户还是集群管理员(即是否属于
adminTeam)。调用方可访问的 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)。
端到端流程示例
安装JSON处理器jq。
以下脚本完整演示了从管理员初始化资源环境与租户凭证,到租户自主实现团队密钥全生命周期管理的端到端业务流程。
#!/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 数组,每个元素的字段说明如下表。
字段 | 类型 | 说明 |
| string | Team 的 UUID,仅用于展示。 |
| string | Team 名称,与 Kubernetes Namespace 一致。 |
| string | 该字段始终返回空字符串,仅为兼容 E2B SDK Schema 保留。如需获取 API Key 明文,请使用 |
| 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"])
响应字段说明:
字段 | 类型 | 说明 |
| string | API Key 的 UUID,删除时使用。 |
| string | Key 的可读名称。 |
| string | Key 的创建时间。 |
| string | 创建该 Key 的调用方 UUID。 |
| string | Key 最近一次使用时间,从未使用时为 |
创建 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"])
请求体字段:
字段 | 类型 | 是否必填 | 说明 |
| string | 是 | Key 的可读名称。 |
| string | 否 | 目标 Team 名称,默认为调用者自身所属的 Team。仅管理员可指向其他 Team,且必须指向一个已经存在的 Kubernetes Namespace。普通租户必须省略此字段。 |
响应字段:
字段 | 类型 | 说明 |
| string | 新 Key 的 UUID,后续删除时使用。 |
| string | 明文 Key,仅返回一次,必须立即保存。 |
| string | 与请求体一致的 Key 名称。 |
| string | Key 实际归属的 Team UUID。 |
| 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 状态码 | 场景 |
| 请求体不合法(JSON 解析失败)或目标 Namespace 不存在。 |
| 未携带 |
| 非管理员对其他 Team 进行写操作,或尝试删除 admin API Key。 |
| 指定 UUID 的 API Key 不存在,或路径参数不是合法 UUID 字符串。 |
| 服务器内部错误,例如缺少必填字段、Kubernetes API 不可用及持久化存储不可达等。 |
错误响应体为 JSON,包含 code、message、request_id 三个字段,便于定位问题:
{
"code": 403,
"message": "You are not allowed to create an API key for another team",
"request_id": "c42c84d8-8420-4194-b48d-5bc5c8fe4ed6"
}