diff --git a/docs.json b/docs.json index 567cde9e..3e33ce15 100644 --- a/docs.json +++ b/docs.json @@ -266,6 +266,7 @@ "introduction/manage", "info/projects", "info/api-keys", + "info/api-key-permissions", "info/audit-logs", "info/network-access" ] diff --git a/info/api-key-permissions.mdx b/info/api-key-permissions.mdx new file mode 100644 index 00000000..4333580f --- /dev/null +++ b/info/api-key-permissions.mdx @@ -0,0 +1,85 @@ +--- +title: "API Key Permissions" +description: "Actions and resources you can grant in a scoped API key policy" +--- + +This page lists everything you can grant in a scoped API key's `policy`. To create a scoped key, see [Scope a key to specific resources](/info/api-keys#scope-a-key-to-specific-resources). + +## Resources + +| Resource | Matches | +| --- | --- | +| `organizations/{org_id}/*` | Everything in the organization. | +| `projects/{project_id}/*` | Everything in the project. | +| `projects/{project_id}/{kind}/{id}` | One object. `kind` is `browsers`, `profiles`, `vaults`, or `proxies`. | + +Resources must be inside the new key's organization, or its project for a project-scoped key. Kernel checks that every object ID exists when you create the key. + +## Actions + +### Browsers + +| Action | Allows | +| --- | --- | +| `browsers:create` | Create browsers. Grant it on a project or organization. | +| `browsers:use` | List, inspect, connect to, and control browsers. | +| `browsers:write` | Update browser configuration. | +| `browsers:delete` | Delete browsers. | + +### Profiles + +| Action | Allows | +| --- | --- | +| `profiles:create` | Create profiles. Grant it on a project or organization. | +| `profiles:read` | List profiles and view their metadata. | +| `profiles:use` | Attach profiles to browsers and download them. | +| `profiles:write` | Rename profiles and save browser state into them. | +| `profiles:delete` | Delete profiles. | + +### Vaults + +| Action | Allows | +| --- | --- | +| `vaults:create` | Create vaults. Grant it on a project or organization. | +| `vaults:read` | List vaults and view vault and item metadata. | +| `vaults:use` | Link vaults to browsers, fill credentials, and run vault item operations. | +| `vaults:write` | Create, update, and delete vault items. | +| `vaults:delete` | Delete vaults and their items. | + +### Proxies + +| Action | Allows | +| --- | --- | +| `proxies:create` | Create proxies. Grant it on a project or organization. | +| `proxies:read` | List proxies and view their configuration, without credentials. | +| `proxies:use` | Select saved proxies for browsers and run health checks. | +| `proxies:write` | Rename proxies. | +| `proxies:delete` | Delete proxies. | + +### Projects and organizations + +| Action | Allows | +| --- | --- | +| `projects:read` | View project metadata and limits. | +| `organizations:read` | View organization entitlements and limits. | + +## Operations that need more than one action + +| Operation | Requires | +| --- | --- | +| Create a browser | `browsers:create` and `browsers:use` on the project or organization, because the response includes connection URLs. Add `profiles:use` on its profile, `profiles:write` if it saves profile changes, `vaults:use` on each linked vault, and `proxies:use` on a saved proxy. | +| Any operation on an existing browser | The browser action, plus `profiles:use` on its attached profile and `vaults:use` on every linked vault. If the browser saves profile changes, `profiles:write` on that profile too. | +| Update a browser | `browsers:write` and `browsers:use`, plus `use` on any profile, vault, or proxy the update adds. | +| Fill from a vault | `vaults:use` on the vault and `browsers:use` on the browser. | +| Run a proxy health check | `proxies:use` and `proxies:read`. | + +List endpoints return only the objects the key can access, so a key without a matching grant gets an empty list. + +## Not available to scoped keys + +Scoped keys get `403` with `insufficient_scope` on: + +- API key management, including creating, listing, rotating, and deleting keys. +- Browser pools. +- Creating browsers that use saved extensions, telemetry export, or app invocations. +- Any other endpoint not covered by the actions above. diff --git a/info/api-keys.mdx b/info/api-keys.mdx index 8d7ae74b..92f2daf2 100644 --- a/info/api-keys.mdx +++ b/info/api-keys.mdx @@ -88,6 +88,82 @@ func main() { ``` +## Scope a key to specific resources + +By default, an API key can do anything in its organization or project. Add a `policy` when you create a key to limit what it can do. Keys created without `policy` work exactly as before. + +Use scoped keys when you hand a credential to code you trust less than your backend, such as an AI agent or a short-lived job. + +### How policies work + +A policy is a list of **statements**. Each statement allows one **action** on one **resource**, and the key is denied everything no statement allows. + +- A **resource** is either one object, `projects/{project_id}/{kind}/{id}`, or everything in a container, `projects/{project_id}/*` or `organizations/{org_id}/*`. +- An **action** is `{kind}:{verb}`, such as `browsers:use` or `vaults:read`. + +Some operations need more than one action. For example, creating a browser with a profile needs `browsers:create` and `browsers:use` on the project, plus `profiles:use` on that profile. See [API key permissions](/info/api-key-permissions) for every action and what each operation requires. + +### Create a scoped key + +This key can create and control browsers in one project, only with profile `prof_7h2kq9x4m1` or no profile, and expires in one hour: + + +```typescript TypeScript +import Kernel from '@onkernel/sdk'; + +const kernel = new Kernel({ + apiKey: process.env.KERNEL_API_KEY, +}); + +const apiKey = await kernel.apiKeys.create({ + name: 'checkout-agent-run-4812', + project_id: 'proj_staging_9f3k', + expires_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(), + policy: { + statements: [ + { action: 'browsers:create', resource: 'projects/proj_staging_9f3k/*' }, + { action: 'browsers:use', resource: 'projects/proj_staging_9f3k/*' }, + { action: 'profiles:use', resource: 'projects/proj_staging_9f3k/profiles/prof_7h2kq9x4m1' }, + ], + }, +}); + +console.log(apiKey.key); // Save this value now. Kernel won't show it again. +``` + +```python Python +import os +from datetime import datetime, timedelta, timezone + +from kernel import Kernel + +client = Kernel(api_key=os.environ["KERNEL_API_KEY"]) + +api_key = client.api_keys.create( + name="checkout-agent-run-4812", + project_id="proj_staging_9f3k", + expires_at=datetime.now(timezone.utc) + timedelta(hours=1), + policy={ + "statements": [ + {"action": "browsers:create", "resource": "projects/proj_staging_9f3k/*"}, + {"action": "browsers:use", "resource": "projects/proj_staging_9f3k/*"}, + {"action": "profiles:use", "resource": "projects/proj_staging_9f3k/profiles/prof_7h2kq9x4m1"}, + ], + }, +) + +print(api_key.key) # Save this value now. Kernel won't show it again. +``` + + +### Expiry and revocation + +- Use `expires_at` for an exact expiry, such as minutes from now, instead of `days_to_expire`. A scoped key can't outlive the key that creates it. +- An expired key is rejected immediately, and a deleted key within about 10 seconds. Neither closes browser connections that are already open, so delete those browsers to cut off access. +- Rotating a scoped key keeps its policy. + +Scoped keys can't manage API keys, and they get `403` on endpoints that don't support scoped access yet. + ## Deployment API keys When you deploy an app, Kernel mints a **deployment-scoped API key** for that deployment and injects it into the deployment (and every invocation it runs) as the `KERNEL_API_KEY` environment variable. Because the SDKs read `KERNEL_API_KEY` from the environment by default, your app can call the Kernel API as itself without you managing a key. @@ -102,7 +178,7 @@ If you need a credential whose lifetime you control (for CI, a persistent backen ## List and inspect API keys -List keys to audit what exists. List and retrieve responses include `masked_key`, `project_id`, `project_name`, `created_by`, and expiry metadata, but they don't include the plaintext key. +List keys to audit what exists. List and retrieve responses include `masked_key`, `project_id`, `project_name`, `created_by`, expiry metadata, and `policy` for scoped keys, but they don't include the plaintext key. ```typescript TypeScript @@ -228,7 +304,7 @@ func main() { ## Rotate a key -`rotate` issues a replacement key in a single call and keeps the old key working for a short grace period, so your workload can switch over without downtime. The new key copies the rotated key's name and project scope, and—like create—Kernel returns the plaintext `key` only once. +`rotate` issues a replacement key in a single call and keeps the old key working for a short grace period, so your workload can switch over without downtime. The new key copies the rotated key's name, project scope, and policy, and—like create—Kernel returns the plaintext `key` only once. Two optional parameters control the timing: @@ -308,5 +384,7 @@ To cut over immediately instead of using a grace window, pass `expire_in_days: 0 | --- | --- | --- | | `400 Bad Request` | The name is missing, `days_to_expire` is outside `1`-`3650`, `expire_in_days` is outside `0`-`3650`, or `project_id` is empty. | Send a name, choose a valid expiry, or omit `project_id` for an org-scoped key. | | `400 Bad Request` (rotate) | `days_to_expire` is shorter than `expire_in_days`, so the new key would expire before the old key's grace window ends. | Raise `days_to_expire` or lower `expire_in_days`. | +| `400 Bad Request` (scoped key) | The policy has an unknown action or a resource outside the key's organization or project, or the expiry is invalid or later than your own key's. | Check the policy against [API key permissions](/info/api-key-permissions) and send one expiry field. | | `401 Unauthorized` | Kernel couldn't authenticate the request. | Set a valid `KERNEL_API_KEY`. | -| `404 Not Found` | The project or API key doesn't exist, or the caller can't access it. | Check the ID. If you're using a project-scoped key, you can only rotate keys in that same project. | +| `403 Forbidden` (`insufficient_scope`) | A scoped key is missing a permission. | The message names the missing action and resource. Add it to the policy. | +| `404 Not Found` | The project, API key, or a resource named in the policy doesn't exist, or the caller can't access it. | Check the ID. If you're using a project-scoped key, you can only rotate keys in that same project. |