Ctrl K
ComunidadGuías…

docs/integrations/databricks.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# databricks

One connection is one Databricks workspace. toolpass authenticates as a service principal (OAuth
M2M) or with a personal access token, finds the user through the workspace SCIM API (email, active
flag, groups), and asks the workspace itself. Unity Catalog securables (catalogs, schemas, tables,
volumes, functions, models) are answered from the effective-permissions endpoint, which folds in
privileges inherited down the hierarchy; grants to the user and to any of the user's groups count,
and the securable's owner holds every privilege. Workspace objects (clusters, jobs, warehouses,
notebooks, ...) are answered from the Permissions API ACL, whose entries include inherited ones;
workspace admins hold `CAN_MANAGE` on every object. Nothing is written.

## Credential

1. Create a service principal in the account console, add it to the workspace, and generate an
   OAuth secret for it (`client_id` is its application id, `credential` the secret). A personal access
   token of a dedicated user works too (`auth_mode: token`).
2. Decide what it may read. Databricks has **no read-only admin role**:
   - the Permissions API returns an object's ACL only to a principal with `CAN_MANAGE` on that object
     (or a workspace admin);
   - Unity Catalog returns another principal's grants only to a metastore admin, the securable's
     owner, the owner of its catalog or schema, or a principal with `MANAGE` on it.

   Making the service principal a **workspace admin** answers every workspace-object question, and
   granting it `MANAGE` (or `BROWSE` plus ownership through a group) on the catalogs it should answer
   for covers Unity Catalog. A workspace admin can also change the workspace, so keep the secret
   tightly held. Whatever toolpass cannot read answers `unknown`, never `deny`.

`toolpass probe` reads the credential's own SCIM record and says whether it is a workspace admin.

## Connection

```yaml
  - id: databricks-prod
    integration: databricks
    url: https://adb-1234567890123456.7.azuredatabricks.net
    client_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890   # service principal application id
    credential: env:DATABRICKS_OAUTH_SECRET            # its OAuth secret
    # auth_mode: token                                 # then credential is a personal access token
    # admins_manage_all: "true"                        # workspace admins pass every workspace-object check
```

| Key | Meaning |
|---|---|
| `url` | the workspace URL |
| `auth_mode` | `oauth` (default): client credentials with `client_id` and the secret; `token`: a personal access token |
| `client_id` | the service principal's application id, `oauth` only |
| `credential` | the OAuth secret or the personal access token, `env:` or `file:` |
| `token_url` | default `{url}/oidc/v1/token` |
| `admins_manage_all` | `true` (default): a member of the workspace `admins` group is allowed every workspace-object action, as Databricks grants admins `CAN_MANAGE` on every object; `false`: judge admins by the ACL alone |

OAuth: `POST {token_url}` with `grant_type=client_credentials&scope=all-apis` and the client id and
secret in an HTTP Basic header, as Databricks documents. The token is cached and refreshed five
minutes before it expires; a 401 from the workspace drops it and retries the call once. Every API
call carries `Authorization: Bearer`.

### Identity

`GET /api/2.0/preview/scim/v2/Users?filter=userName eq "<email>"&attributes=id,userName,active,groups`.
The email is lowercased and must be a plain address (no quotes or backslashes, so the SCIM filter
cannot be broken out of); the record whose `userName` equals it case-insensitively is the user. No
record is `user_not_found`, two are `user_ambiguous`. A user with `active: false` is denied every
action; a record without `active` is unknown. The user's groups are the `groups[].display` names of
the record; membership of `admins` marks a workspace admin. Groups sent by the caller are ignored.

## Resources

| Resource | Meaning |
|---|---|
| `catalog:<catalog>` | a Unity Catalog catalog |
| `schema:<catalog>.<schema>` | a schema |
| `table:<catalog>.<schema>.<table>` | a table or view |
| `volume:<catalog>.<schema>.<volume>` | a volume |
| `function:<catalog>.<schema>.<function>` | a function |
| `model:<catalog>.<schema>.<model>` | a registered model in Unity Catalog |
| `cluster:<id>` | an all-purpose cluster (Permissions API `clusters`) |
| `policy:<id>` | a cluster policy (`cluster-policies`) |
| `pool:<id>` | an instance pool (`instance-pools`) |
| `job:<id>` | a job (`jobs`) |
| `pipeline:<id>` | a pipeline (`pipelines`) |
| `warehouse:<id>` | a SQL warehouse (`warehouses`) |
| `notebook:<object id>`, `directory:<object id>`, `repo:<id>` | workspace files (`notebooks`, `directories`, `repos`); the numeric object id, not the path |
| `endpoint:<id>` | a model serving endpoint (`serving-endpoints`): the endpoint's `id`, not its name, as the Permissions API keys it |
| `experiment:<id>`, `registered_model:<id>` | MLflow experiments and workspace model registry models |

Each part of a Unity Catalog name must be a plain identifier (`[A-Za-z0-9_-]`, up to 255 characters);
names that need backtick quoting in SQL are not accepted. A workspace object id is
`[A-Za-z0-9][A-Za-z0-9_.-]*`. Both are path-escaped before they reach a URL.

## Actions

| Action | Requires | Resources |
|---|---|---|
| `raw:<PRIVILEGE>` | that one Unity Catalog privilege (`raw:SELECT`, `raw:CREATE_VOLUME`) | any Unity Catalog resource |
| `raw:<LEVEL>` | that permission level or one that implies it (`raw:CAN_RESTART`, `raw:IS_OWNER`); a level the object type does not have is `invalid_request` | any workspace object |
| `table.read` | `SELECT` + `USE_SCHEMA` + `USE_CATALOG` | table |
| `table.write` | `MODIFY` + `USE_SCHEMA` + `USE_CATALOG` | table |
| `table.create` | `CREATE_TABLE` + `USE_SCHEMA` + `USE_CATALOG` | schema |
| `volume.create` / `model.create` | `CREATE_VOLUME` / `CREATE_MODEL` + `USE_SCHEMA` + `USE_CATALOG` | schema |
| `schema.create` | `CREATE_SCHEMA` + `USE_CATALOG` | catalog |
| `schema.read` | `USE_SCHEMA` + `USE_CATALOG` | schema |
| `catalog.use` | `USE_CATALOG` | catalog |
| `volume.read` / `volume.write` | `READ_VOLUME` / `WRITE_VOLUME` + `USE_SCHEMA` + `USE_CATALOG` | volume |
| `function.execute` | `EXECUTE` + `USE_SCHEMA` + `USE_CATALOG` | function |
| `model.read` | `EXECUTE` + `USE_SCHEMA` + `USE_CATALOG` | model |
| `uc.manage` | `MANAGE`, or ownership | any Unity Catalog resource |
| `schema.manage` | `MANAGE` (or ownership) + `USE_CATALOG` | schema |
| `table.manage` / `volume.manage` / `function.manage` / `model.manage` | `MANAGE` (or ownership) + `USE_SCHEMA` + `USE_CATALOG` | table / volume / function / model |
| `cluster.attach` / `cluster.restart` / `cluster.manage` | `CAN_ATTACH_TO` / `CAN_RESTART` / `CAN_MANAGE` | cluster |
| `job.view` / `job.run` / `job.manage` | `CAN_VIEW` / `CAN_MANAGE_RUN` / `CAN_MANAGE` | job |
| `warehouse.use` / `warehouse.manage` | `CAN_USE` / `CAN_MANAGE` | warehouse |
| `notebook.read` / `notebook.run` / `notebook.edit` | `CAN_READ` / `CAN_RUN` / `CAN_EDIT` | notebook, directory, repo |
| `pipeline.run` | `CAN_RUN` | pipeline |
| `endpoint.query` | `CAN_QUERY` | endpoint |

Unity Catalog: `ALL_PRIVILEGES` covers every privilege that lives on the securable it was granted on
and below: granted on the catalog it carries `USE_CATALOG` and `USE_SCHEMA`, granted on a schema it
carries `USE_SCHEMA` but not `USE_CATALOG`, granted on a table it carries neither. It never includes
`MANAGE`. The legacy `USAGE` counts as both `USE_CATALOG` and `USE_SCHEMA`. Privileges are read from
`GET /api/2.1/unity-catalog/effective-permissions/{type}/{name}?max_results=0`, every page, and
unioned over the user and the user's groups. When they do not cover the action, the securable's
`owner` is read (`GET /api/2.1/unity-catalog/{tables,schemas,...}/{name}`). Ownership, directly or
through a group, stands for every privilege on the securable itself, but not for `USE_CATALOG` or
`USE_SCHEMA` on its parents: a table owner without `USE_SCHEMA` is denied `table.read`, as Databricks
would refuse the query.

Unity Catalog shows a principal without `MANAGE`, ownership or metastore admin only its own grants,
with a 200 rather than a 403. A listing in which no principal but toolpass itself appears is
therefore not a deny: it is answered `resource_not_visible` ("either nobody else holds any, or
toolpass may only see its own"). toolpass learns its own principal name from `GET
/api/2.0/preview/scim/v2/Me`, once every ten minutes.

Workspace objects: the ACL is `GET /api/2.0/permissions/{type}/{id}`. A level implies the weaker ones
of its chain (`CAN_ATTACH_TO` < `CAN_RESTART` < `CAN_MANAGE`; `CAN_VIEW` < `CAN_MANAGE_RUN` <
`IS_OWNER` < `CAN_MANAGE`; `CAN_READ` < `CAN_RUN` < `CAN_EDIT` < `CAN_MANAGE`; `CAN_VIEW` <
`CAN_QUERY` < `CAN_MANAGE`; warehouses `CAN_VIEW` < `CAN_MONITOR` and `CAN_VIEW` < `CAN_USE`, both
under `CAN_MANAGE`), and `CAN_MANAGE` and `IS_OWNER` imply everything except `IS_OWNER` itself, which
names the one owner and is matched exactly (workspace admins included). `CAN_MONITOR` does not imply
`CAN_USE`. Entries for service principals never match a user.

## Decisions

| Situation | toolpass answers |
|---|---|
| the needed privileges are all held (directly, through a group, or inherited from a parent securable), or covered by an `ALL_PRIVILEGES` grant at the right level | allow, saying where they come from |
| privileges missing but the user (or a group of theirs) owns the securable | allow ("owns ...") |
| privileges missing, the owner, but `USE_CATALOG` / `USE_SCHEMA` on a parent missing | deny ("owns ... but lacks ... on its parents") |
| privileges missing, not the owner, and the listing names a principal other than toolpass | deny, naming the missing privileges |
| privileges missing, not the owner, and the listing names nobody but toolpass | unknown (`resource_not_visible`) |
| a held level equals or implies the needed one | allow |
| no matching level, user is a workspace admin, `admins_manage_all: true` | allow ("workspace admin") |
| no matching level otherwise | deny, naming the levels held |
| deactivated user (`active: false`) | deny |
| no SCIM record for the email | deny (`user_not_found`) |
| several records | unknown (`user_ambiguous`) |
| record without `active` | unknown (`unsupported`) |
| securable or object answers 404 | unknown (`resource_not_visible`) |
| 403 `PERMISSION_DENIED` (toolpass lacks `CAN_MANAGE`, `MANAGE` or ownership) | unknown (`credential_rejected`) |
| 401 after one retry with a fresh token, token endpoint refuses the client | unknown (`credential_rejected`) |
| the SCIM search endpoint itself answers 404 (wrong `url`, an account console URL) | unknown (`upstream_error`) |
| 400 `INVALID_PARAMETER_VALUE` | unknown (`invalid_request`) |
| 429, 5xx, timeout, too many pages of grants | unknown (`upstream_rate_limited` / `upstream_error` / `upstream_timeout`) |

Only `error_code` is read from an error body; messages are never copied into a decision text.

## Probe

`toolpass probe` calls `GET /api/2.0/preview/scim/v2/Me`, names the principal and whether it is a
workspace admin, and warns either way: a non-admin cannot read most ACLs, an admin can change the
workspace.

## Intercepted requests

A toolpass tool's requests to the workspace REST API are mapped to these actions
(`src/toolpass/integrations/databricks/routes.py`, `toolpass routes databricks`): jobs by `job_id`
(get, runs of a job, run now, cancel all runs, delete), clusters by `cluster_id` (get, events, start,
restart, terminate, resize, delete), SQL warehouses, pipelines and Git folders by id, ACLs through
the Permissions API (`CAN_MANAGE` on the object; a body that grants `IS_OWNER` anywhere, or spells
`access_control_list` another way, is refused), Unity Catalog catalogs, schemas, tables, volumes,
functions and models by full name, and files under `/Volumes/<catalog>/<schema>/<volume>` as
`volume.read` and `volume.write`. Unity Catalog requests carry the `USE_CATALOG` and `USE_SCHEMA`
Databricks requires on the securable's parents: reads are `catalog.use`, `schema.read`,
`table.read`, `volume.read`, `function.execute` and `model.read` (a model's versions too); updates,
deletes, renames within the same parent and grants are `<type>.manage` (`uc.manage` on a catalog);
creating schemas, managed volumes and models is `schema.create`, `volume.create` and `model.create`
on the parent. Lists need no user check; the workspace filters them by the agent's own access.

Refused: SQL statement execution, the command execution API and one-time job runs (they run as the
agent), runs named only by `run_id`, edits Databricks checks against the caller (job and pipeline
settings, cluster edits), creating jobs, clusters, warehouses, catalogs, tables, views and functions,
the Workspace and DBFS APIs (they name objects by path), secrets, model serving endpoints addressed
by name (reading, querying, updating or deleting one: the serving API takes the endpoint's name,
while its permissions are kept under its id, which the request does not carry; an endpoint's ACL,
`/api/2.0/permissions/serving-endpoints/<id>`, is routed), and any request that names its id twice
or in a form toolpass does not read exactly.

## What it cannot see

- Metastore admins: their implicit privileges are not in SCIM or in the effective-permissions list,
  so a metastore admin without grants is denied Unity Catalog actions.
- Ownership of a parent (a catalog owner acting on a table) is not modelled; only the securable's
  own owner is, and only for privileges on that securable.
- Account-level groups the workspace SCIM record does not list, row filters and column masks,
  Lakehouse Federation credentials, table ACLs on the legacy Hive metastore, and the entitlements
  (`workspace-access`, `databricks-sql-access`, `allow-cluster-create`) that gate features rather than
  objects.
- Whether a cluster's access mode lets the user attach at all, and cluster policies' effect on
  what a job may run.

## Unverified

Marked `# UNVERIFIED:` in the code:

- Whether the effective-permissions endpoint already lists the owner's implicit privileges. The owner
  is looked up separately so an owner is never denied; if the API does list them the extra call is
  merely redundant.

Not marked in code, but assumed from Databricks' documentation rather than tested live: that a
`principal=`-less effective-permissions listing includes group principals by their display name, so
unioning over the user's SCIM groups is what "effective" means; that the workspace `admins` group
carries `CAN_MANAGE` on every Permissions API object even when the ACL does not show an entry for it;
and that `IS_OWNER` on jobs, pipelines and warehouses implies `CAN_MANAGE`.

## Test

Unit tests run against one fake server that serves the token endpoint (checking the Basic header and
the form), SCIM, effective permissions (paginated), securable metadata and the Permissions API.
Databricks publishes no OpenAPI description to validate requests against, so the fake checks paths
and parameters itself. There is no live test; after configuring, run `toolpass probe` and one check
for a user you know is allowed.