multi-tenancy management
When multiple tenants share an Agent Sandbox cluster, each tenant is mapped to a dedicated Kubernetes Namespace through a Team and uses its own API key as an access credential. Administrators and tenants can create, query, and revoke API keys through HTTP interfaces.
Overview
ack-sandbox-manager provides a set of E2B-compatible HTTP interfaces to manage API keys and Teams. Cluster administrators and tenants can use these interfaces to programmatically create API keys, query their associated Teams, and revoke unneeded keys.
These interfaces do not have a corresponding E2B SDK or Kubernetes Custom Resource Definitions (CRDs). You must call them directly over HTTP.
Team
A Team defines the authorization boundary for an API key. In Agent Sandbox, a Team is uniquely identified by its name, which maps to a Kubernetes Namespace.
The built-in
adminTeam is cluster-level, managed by the cluster administrator. Its admin API key is initialized whenack-sandbox-managerstarts.Any other Team name must correspond to an existing Kubernetes Namespace. When a tenant creates an API key for Team
foo, the namespacefoomust already exist. Otherwise, the request returns an error.
Because a namespace's uniqueness provides a sufficient isolation boundary, the Team's unique identifier (UUID) serves only as display metadata and is not used for authentication or resource lookups.
API key
An API key is a long-term access credential held by a Team. A client must include it in the X-API-KEY request header for every call to any ack-sandbox-manager interface. The API key determines:
The Team to which the caller belongs.
Whether the caller is a regular tenant or a cluster administrator (that is, a member of the
adminTeam).The scope of sandboxes the caller can access. Regular tenants can access only the sandboxes created with their own API key, whereas administrators can access all sandboxes.
Authorization model
Role | List keys (current team) | List teams | Create key (current team) | Create key (other team) | Delete key (current team) | Delete admin key |
Administrator (admin Team) | Supported | Supported (all Teams) | Supported | Supported | Supported | Not supported (denied) |
Regular tenant | Supported | Supported (own Team only) | Supported | Not supported | Supported | Not supported |
To protect cluster manageability, the built-in admin API key cannot be deleted under any circumstances.
Before you begin
In your target cluster, go to the Add-ons page. Confirm that
ack-sandbox-managerv0.6.0 or later is installed. Then, on the component's Configuration page, apply the following settings:Select the Whether to enable E2B_API_KEY authentication checkbox to enable API key authentication.
Configure the adminApiKey (administrator API key).
End-to-end example
Obtain the KubeConfig file for the cluster and use kubectl to connect to the cluster.
Install the JSON processor jq.
This script demonstrates the end-to-end process: a cluster administrator initializes the resource environment and tenant credentials, and then a tenant manages their Team's key lifecycle.
#!/usr/bin/env bash set -euo pipefail ADMIN_KEY="${ADMIN_KEY:?required}" # Two protocols are supported. This example uses the native E2B protocol URL: https://api.your.domain.com # To use the Agent Sandbox private protocol, replace BASE_URL with https://your.domain.com/kruise/api. BASE_URL="${BASE_URL:-https://api.your.domain.com}" TEAM_NAMESPACE="team-a" # 1. The administrator creates the namespace for the Team in the cluster. kubectl create ns "${TEAM_NAMESPACE}" # 2. The administrator creates a tenant key for team-a. Record the plaintext key from the response. 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. The tenant lists their Team and existing keys. curl -fsS -H "X-API-KEY: ${TENANT_KEY}" "${BASE_URL}/teams" curl -fsS -H "X-API-KEY: ${TENANT_KEY}" "${BASE_URL}/api-keys" # 4. The tenant creates a sub-key for their Team (the teamName field is not required). 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. The tenant deletes a sub-key from their Team. curl -fsS -X DELETE \ -H "X-API-KEY: ${TENANT_KEY}" \ "${BASE_URL}/api-keys/${SUB_KEY_ID}"
API reference
List teams
Lists the Teams visible to the current user. Regular tenants can see only their own Team. An administrator can see all Teams.
Request example:
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())
Response example:
[
{
"teamID": "550e8400-e29b-41d4-a716-446655449999",
"name": "admin",
"apiKey": "",
"isDefault": true
}
]The response body is a JSON array. The following table describes the fields for each element.
Field | Type | Description |
| string | The UUID of the Team. This is for display purposes only. |
| string | The name of the Team, which is the same as the corresponding Kubernetes Namespace. |
| string | This field always returns an empty string and is reserved for compatibility with the E2B SDK schema. To get the plaintext key, use the |
| boolean | Indicates if this is the caller's default Team. |
List API keys
Lists all API keys that belong to the caller's Team.
Request example:
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"])
Response fields:
Field | Type | Description |
| string | The UUID of the API key, used for deletion. |
| string | The human-readable name of the key. |
| string | The time when the key was created. |
| string | The UUID of the key's creator. |
| string | The time when the key was last used. The value is |
Create an API key
The response body includes the plaintext key. This key is returned only once upon creation and cannot be retrieved again. You must save it immediately.
Before running the example, ensure that the team-a namespace already exists. If the caller is a regular tenant, omit the teamName field from the request body.
Request example:
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()
# You must save created["key"] immediately, as it cannot be retrieved again.
print(created["id"], created["key"])
Request body fields:
Parameter | Type | Required | Description |
| string | Yes | The human-readable name of the key. |
| string | No | The name of the target Team. Defaults to the caller's own Team. Only a cluster administrator can specify another Team, which must correspond to an existing Kubernetes Namespace. Regular tenants must omit this field. |
Response fields:
Field | Type | Description |
| string | The new key's UUID, used for deletion. |
| string | The plaintext key. This value is returned only once and must be saved immediately. |
| string | The key name, as specified in the request. |
| string | The UUID of the Team that owns the key. |
| string | The name of the Team to which the key actually belongs (the corresponding Kubernetes Namespace). This helps you verify the caller's intent. |
Delete an API key
Deletes a specified API key by its UUID. Regular tenants can delete only keys from their own Team. An administrator can delete any key except the admin API key. Attempting to delete the built-in admin API key returns a 403 Forbidden error.
Request example:
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,
)
# A successful request returns 204 No Content.
try:
resp.raise_for_status()
except requests.HTTPError:
print(resp.text)
raise
This endpoint returns a 404 Not Found error for both invalid and non-existent UUIDs. The interface does not distinguish between these two cases.Error codes
HTTP status code | Scenario |
| The request body is invalid (JSON parsing failed), or the target namespace does not exist. |
| The |
| A regular tenant attempts a write operation on another Team, or attempts to delete the admin API key. |
| The API key with the specified UUID does not exist, or the path parameter is not a valid UUID. |
| An internal server error occurred. For example, a required field is missing, the Kubernetes API is unavailable, or the persistent storage is unreachable. |
The JSON error response body includes the code, message, and request_id fields to aid troubleshooting:
{
"code": 403,
"message": "You are not allowed to create an API key for another team",
"request_id": "c42c84d8-8420-4194-b48d-5bc5c8fe4ed6"
}