apache / apache/polaris

[FEATURE REQUEST] listCatalogs should return the catalogs a caller can access, instead of requiring service-admin (CATALOG_LIST on root)

Open
#4,573 5 comments 1 reaction 0 assignees View on GitHub
Dominant language
Java
Stars
2.1k
Forks
522
Avg merge
1d 17h
Merged PRs (30d)
137

Description

### Is your feature request related to a problem? Please describe.

`GET /api/management/v1/catalogs` (`listCatalogs`) is authorized by `CATALOG_LIST` on the **root container** — effectively a `service_admin` capability — and it is all-or-nothing: a principal lacking that root privilege gets a hard `403`, not a filtered list. A least-privilege principal therefore cannot *discover* catalogs even for catalogs it has been explicitly granted access to.

Concretely, with a principal whose catalog role grants `CATALOG_READ_PROPERTIES` / `NAMESPACE_LIST` / `TABLE_LIST` / `TABLE_READ_DATA` on specific catalogs:

- `GET /api/management/v1/catalogs` → `403` (`Principal '…' … is not authorized for op LIST_CATALOGS`)
- `GET /api/management/v1/catalogs/{name}` (`getCatalog`) → `200`
- Iceberg catalog API (list namespaces/tables, read data) → works

So the principal can read everything in the catalogs it is granted, but cannot enumerate them. This makes the **polaris-console** catalog browser (which calls `listCatalogs`) and any read-only "viewer" persona unusable for non-admins — the catalog list is empty/forbidden.

And there is no narrower fix: `CATALOG_LIST` must sit on the root container, the only code path that places a privilege there (`PolarisAdminService.grantPrivilegeOnRootContainerToPrincipalRole`, op `ADD_ROOT_GRANT_TO_PRINCIPAL_ROLE`, itself requiring `SERVICE_MANAGE_ACCESS`) is **not exposed via the management REST API**, so the only API-reachable way to make `listCatalogs` succeed is to grant `service_admin` (full administrative access: create/drop catalogs, manage all principals/roles/grants) — the opposite of least privilege.

Relevant code (`apache-polaris-1.3.0-incubating`; authorization path unchanged on `main`):

- `PolarisAdminService.listCatalogs()` → `authorizeBasicRootOperationOrThrow(LIST_CATALOGS)`, then `listCatalogsUnsafe()` returns **all** catalogs (no filtering).
- `authorizeBasicRootOperationOrThrow(...)` resolves the **root container** as the authorization target.
- `RbacOperationSemantics`: `register(LIST_CATALOGS, CATALOG_LIST)`.
- `PolarisAuthorizerImpl`: `CATALOG_LIST` is satisfied by `SERVICE_MANAGE_ACCESS` (or catalog-level `CATALOG_READ_PROPERTIES`, etc.) **on the same securable** — i.e. on the root container, which a non-admin never holds.

### Describe the solution you'd like

When the caller does not hold `CATALOG_LIST` on the root container, `listCatalogs` should return the **subset of catalogs the caller is authorized on** (holds any catalog-role grant / `CATALOG_READ_PROPERTIES`), instead of throwing `403`. Callers with root `CATALOG_LIST` / `SERVICE_MANAGE_ACCESS` continue to see all catalogs.

This reuses the same per-securable check `getCatalog` already performs, mirrors how `listNamespaces` / `listTables` are scoped within a catalog, and matches how object stores filter list results by ACL. Because each catalog is itself a securable with grants (unlike principal-roles in #363), filtering the catalog list by the caller's effective grants is straightforward. Result: non-admins discover exactly the catalogs they can use, the console works for read-only personas, and no `service_admin` grant or root-container grant API is required.

### Describe alternatives you've considered

- **Expose root-container grants via REST** so `CATALOG_LIST`-only can be granted to a principal role — enables a "list, no write/drop" role, but is still all-or-nothing ("see all catalogs") and adds a privilege that is hard to reason about; filtering is more useful and least-surprising.
- **`service_admin`** (current workaround) — over-privileged (can drop catalogs, manage all principals); unacceptable for viewer / least-privilege use.
- **A catalog-scoped discovery endpoint** — cumbersome when a principal has access to many catalogs (also noted in #363).

### Additional context

Sibling issue #363 raises the same class of problem for `listPrincipalRoles` under `catalog_admin`. Performance: filtering adds a per-catalog authorization check; catalog counts are typically small and this can be batched if needed. Observed on Apache Polaris 1.3.0-incubating via polaris-console; authorization path verified unchanged on `main`.

Contributor guide

Open the contributing guide

Research direction

Start with PolarisAdminService.listCatalogs(), authorizeBasicRootOperationOrThrow(LIST_CATALOGS), listCatalogsUnsafe(), and getCatalog(). Trace the per-securable checks through RbacOperationSemantics and PolarisAuthorizerImpl. Done means root-authorized callers still see all catalogs, while other callers receive only catalogs they can access instead of a 403; verify the behavior with relevant authorization tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
api, authorization
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.