Roles
Overview
A role is a named set of privileges, granted to users and groups. Privileges are never granted directly to a user, so a role is the only way a privilege reaches anyone.
A role holds objects, and for each one a list of privileges, each carrying an ALLOW or DENY
condition. A privilege binds only to object types it supports, so CREATE_TABLE binds to a metalake,
catalog, or schema, never to a table. Whoever creates a role owns it, and can alter or delete it.
How a request is evaluated against the roles a caller holds, including how DENY interacts with
ALLOW, is described in Access Control.
Quick Start
1. Create a role. Reading one schema takes three privileges: USE_CATALOG and USE_SCHEMA to
reach it, and SELECT_TABLE to read what is in it. Object names are not validated, so a typo
produces a role that grants nothing.
curl -sS -X POST "https://{gravitino_host}/api/metalakes/{metalake}/roles" \
-H "Accept: application/vnd.gravitino.v1+json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "schema_reader",
"properties": {},
"securableObjects": [
{
"fullName": "{catalog}",
"type": "CATALOG",
"privileges": [{"name": "USE_CATALOG", "condition": "ALLOW"}]
},
{
"fullName": "{catalog}.{schema}",
"type": "SCHEMA",
"privileges": [
{"name": "USE_SCHEMA", "condition": "ALLOW"},
{"name": "SELECT_TABLE", "condition": "ALLOW"}
]
}
]
}'
2. Grant it to a group. Granting to a group reaches every member, which is how access is usually
managed for a team. Granting to a single user works the same way with users in the path.
curl -sS -X PUT "https://{gravitino_host}/api/metalakes/{metalake}/permissions/groups/{group}/grant" \
-H "Accept: application/vnd.gravitino.v1+json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"roleNames": ["schema_reader"]}'
Privileges and What They Allow
Grantable On lists the object types a privilege can be bound to, and the object it is bound to sets the scope of the grant. Binding a privilege to a type not listed for it is rejected.
Data Object Privileges
| Privilege | Grantable On | What It Allows |
|---|---|---|
CREATE_CATALOG | Metalake | Create catalogs |
USE_CATALOG | Metalake, Catalog | Use any catalog in scope, and reach the objects inside it |
CREATE_SCHEMA | Metalake, Catalog, Schema | Create schemas or nested schemas in scope |
USE_SCHEMA | Metalake, Catalog, Schema | Use any schema in scope, and reach the objects inside it |
CREATE_TABLE | Metalake, Catalog, Schema | Create tables in any schema in scope |
SELECT_TABLE | Metalake, Catalog, Schema, Table | Read any table in scope |
MODIFY_TABLE | Metalake, Catalog, Schema, Table | Read and write to, and alter the schema of, any table in scope |
CREATE_VIEW | Metalake, Catalog, Schema | Create views in any schema in scope |
SELECT_VIEW | Metalake, Catalog, Schema, View | Read any view in scope |
CREATE_TOPIC | Metalake, Catalog, Schema | Create topics in any schema in scope |
CONSUME_TOPIC | Metalake, Catalog, Schema, Topic | Consume from any topic in scope |
PRODUCE_TOPIC | Metalake, Catalog, Schema, Topic | Consume from, produce to, and alter any topic in scope |
CREATE_FILESET | Metalake, Catalog, Schema | Create filesets in any schema in scope |
READ_FILESET | Metalake, Catalog, Schema, Fileset | Read any fileset in scope |
WRITE_FILESET | Metalake, Catalog, Schema, Fileset | Read, write, and alter any fileset in scope |
REGISTER_MODEL | Metalake, Catalog, Schema | Register models in any schema in scope |
LINK_MODEL_VERSION | Metalake, Catalog, Schema, Model | Link versions to any model in scope |
USE_MODEL | Metalake, Catalog, Schema, Model | Read the metadata of, and download versions of, any model in scope |
REGISTER_FUNCTION | Metalake, Catalog, Schema | Register functions in any schema in scope |
EXECUTE_FUNCTION | Metalake, Catalog, Schema, Function | Read the metadata of, and execute, any function in scope |
MODIFY_FUNCTION | Metalake, Catalog, Schema, Function | Alter any function in scope |
Either SELECT_TABLE or MODIFY_TABLE is enough to load a table's metadata, and the same pairing
holds for views, topics, and filesets.
CREATE_MODEL and CREATE_MODEL_VERSION are deprecated aliases for REGISTER_MODEL and
LINK_MODEL_VERSION. They resolve to identical authorization, so existing grants keep working, but
they will be removed in a future release. Use the current names in new roles.
Governance and Administrative Privileges
| Privilege | Grantable On | What It Allows |
|---|---|---|
MANAGE_USERS | Metalake | Add and remove users |
MANAGE_GROUPS | Metalake | Add and remove groups |
CREATE_ROLE | Metalake | Create roles |
MANAGE_GRANTS | Metalake, Catalog, Schema, Table, View, Topic, Fileset, Model, Function | Grant and revoke privileges on any object in scope |
CREATE_TAG | Metalake | Create tags |
APPLY_TAG | Metalake, Tag | Attach tags to metadata objects |
CREATE_POLICY | Metalake | Create policies |
APPLY_POLICY | Metalake, Policy | Attach policies to metadata objects |
REGISTER_JOB_TEMPLATE | Metalake | Register job templates |
USE_JOB_TEMPLATE | Metalake, JobTemplate | Run jobs from a job template |
RUN_JOB | Metalake | Run jobs |
MANAGE_GRANTS bound to a metalake additionally allows granting and revoking roles for users and
groups across that metalake. Bound to anything else it covers privilege management only, on that
object and its descendants.
APPLY_TAG, APPLY_POLICY, and USE_JOB_TEMPLATE scope differently from every other privilege on
this page. The object they bind to is the instrument the holder may use, not the object the operation
acts on. Granting APPLY_POLICY on the policy pii_masking lets the holder attach that one policy
and no other, while granting it on the metalake lets them attach any policy in the metalake.
Attaching a tag or a policy is checked twice: the holder needs APPLY_TAG or APPLY_POLICY for the
tag or policy in question, and separately needs access to the metadata object being tagged. A user
cannot tag an object they could not otherwise reach.
Required Privileges
Four rules apply throughout, so they are not repeated below:
- Owning the object, or any ancestor of it, satisfies any check on it once the object can be reached. Owner in the tables means ownership is the only route, because no privilege grants that operation.
- Reaching an object inside a catalog and a schema requires
USE_CATALOGandUSE_SCHEMA, unless the caller owns the catalog or schema above it. Owning the object itself does not replace them. - An owner can always read an object's owner and transfer its ownership, with no grants on its parents, so ownership can never be stranded. The UI hides objects whose parents the caller cannot reach, so an owner without those grants makes the transfer through the API.
- A privilege counts whether it is held on the object itself or on any ancestor.
List operations never fail. They return the entries the caller is entitled to see, which for a metalake owner is all of them.
Data Objects
| Object | Create | Load | Alter | Drop |
|---|---|---|---|---|
| Catalog | CREATE_CATALOG | USE_CATALOG | Owner | Owner |
| Schema | CREATE_SCHEMA | USE_SCHEMA | Owner | Owner |
| Table | CREATE_TABLE | SELECT_TABLE or MODIFY_TABLE | MODIFY_TABLE | Owner |
| View | CREATE_VIEW | SELECT_VIEW | Owner | Owner |
| Topic | CREATE_TOPIC | CONSUME_TOPIC or PRODUCE_TOPIC | PRODUCE_TOPIC | Owner |
| Fileset | CREATE_FILESET | READ_FILESET or WRITE_FILESET | WRITE_FILESET | Owner |
| Model | REGISTER_MODEL | USE_MODEL | Owner | Owner |
| Function | REGISTER_FUNCTION | EXECUTE_FUNCTION or MODIFY_FUNCTION | MODIFY_FUNCTION | Owner |
Table statistics follow the table itself: reading them takes SELECT_TABLE or MODIFY_TABLE,
writing them takes MODIFY_TABLE. Model versions follow the model: USE_MODEL to read, owner to
alter or delete. Fetching a credential takes whatever loading the object takes.
Renaming a table or view into a different schema is the one operation needing a privilege on a second
object: the owner of the table or view, plus CREATE_TABLE or CREATE_VIEW on the target schema.
Metalake Objects
| Object | Create | Read | Alter or delete | Use |
|---|---|---|---|---|
| Metalake | Service administrator | Membership | Owner | |
| User | MANAGE_USERS | MANAGE_USERS, or the user themselves | MANAGE_USERS | |
| Group | MANAGE_GROUPS | MANAGE_GROUPS, or a member | MANAGE_GROUPS | |
| Role | CREATE_ROLE | MANAGE_GRANTS, or a holder or owner | Owner | Grant or revoke: MANAGE_GRANTS |
| Tag | CREATE_TAG | APPLY_TAG | Owner | Attach: APPLY_TAG and access to the object |
| Policy | CREATE_POLICY | APPLY_POLICY | Owner | Attach: APPLY_POLICY and access to the object |
| Job template | REGISTER_JOB_TEMPLATE | USE_JOB_TEMPLATE | Owner | Run a job: RUN_JOB and USE_JOB_TEMPLATE |
| Job | Owner | Owner |
Granting or revoking a privilege on an object takes MANAGE_GRANTS on that object or an ancestor.
Granting or revoking a role, and overriding a role's privileges, takes MANAGE_GRANTS on the
metalake. Setting an owner takes ownership.
Narrowing Access with Active Roles
By default, a request is evaluated against every role the caller holds. The X-Gravitino-Active-Roles
header narrows that set for the request that carries it, so a workload runs with only the roles it
needs instead of every role its user has been granted.
X-Gravitino-Active-Roles: analyst,reader
| Value | Meaning |
|---|---|
analyst | Activate one named role |
analyst,reader | Activate several; access is the union of just these |
ALL | Activate every role the caller holds |
NONE | Activate no role |
| (absent or empty) | Same as ALL |
Role names are matched exactly, and ALL and NONE are recognized only in upper case, so all is
read as the name of a role. Surrounding whitespace is trimmed and repeated names collapse.
What Narrowing Changes
Narrowing only ever subtracts. The server validates the declaration against the roles the caller actually holds, so the header can never widen access, and a caller that omits it is evaluated exactly as before.
DENYstays global. A deny carried by any role the caller holds still applies even when that role is not active, so narrowing cannot be used to escape a denial.- Ownership is untouched. Access that comes from owning an object is granted to the owner
directly rather than through a role, so an owner keeps it even under
NONE. - Every decision in the request is narrowed, not only direct checks. List results are filtered
against the active set, and so are the privileges behind credential vending: an Iceberg caller
whose active roles no longer carry
MODIFY_TABLEis vended a read-only storage credential in place of a writable one.
Errors
| Condition | Response |
|---|---|
An empty entry, such as the trailing comma in analyst, | 400 Bad Request |
ALL or NONE combined with anything else, such as ALL,analyst | 400 Bad Request |
| A well-formed value naming a role the caller does not hold | 403 Forbidden |
A role that does not exist and a role the caller was never granted both return 403, so the response
cannot be used to discover which role names exist. An unheld role is rejected rather than ignored,
which surfaces a typo immediately instead of silently reducing access.
Sending the Header
Apache Spark forwards any header.* catalog property to the Iceberg REST catalog:
spark.sql.catalog.{catalog}.header.X-Gravitino-Active-Roles = analyst
Trino 481 and later forwards headers configured on the catalog:
iceberg.rest-catalog.http-headers = X-Gravitino-Active-Roles: analyst
Both are catalog-level and static, so the same value applies to every user and session using that catalog. The Java client sets the header per client instance:
GravitinoClient.builder(uri)
.withMetalake("metalake")
.withHeaders(ImmutableMap.of("X-Gravitino-Active-Roles", "analyst"))
.build();
Scope
Narrowing applies where Gravitino enforces authorization itself: the native REST API and the Iceberg REST catalog. Catalogs that push enforcement down to an external system evaluate against the mapped user and groups and never see the declaration, so the header has no effect there. See Authorization Pushdown.
Endpoints
Paths are relative to https://{gravitino_host}/api/metalakes/{metalake}. For request and response schemas, see the Gravitino REST API.
| Operation | Method | Path |
|---|---|---|
| Create a role | POST | /roles |
| List roles | GET | /roles |
| Get a role | GET | /roles/{role} |
| Delete a role | DELETE | /roles/{role} |
| Grant privileges to a role | PUT | /permissions/roles/{role}/{object_type}/{object_name}/grant |
| Revoke privileges from a role | PUT | /permissions/roles/{role}/{object_type}/{object_name}/revoke |
| Replace a role's privileges | PUT | /permissions/roles/{role}/ |
| List the roles bound to an object | GET | /objects/{object_type}/{object_name}/roles |
Add ?details=true to the list path to get full roles instead of names. Granting a role to a user or
group is covered on Users and Groups.
Replacing a role's privileges is destructive: afterwards the role holds exactly what the request body contains, and any object absent from it is dropped.
Java Client
Most calls map directly onto a GravitinoClient method and are covered by the
Java doc.
Two take arguments that are hard to derive from the signature alone.
A role's objects are built as a nested path rather than a dotted string:
SecurableObject table =
SecurableObjects.ofTable(
SecurableObjects.ofSchema(
SecurableObjects.ofCatalog("catalog1", Collections.emptyList()),
"schema1",
Collections.emptyList()),
"table1",
Lists.newArrayList(Privileges.SelectTable.allow()));
Role role = client.createRole("schema_reader", ImmutableMap.of(), Lists.newArrayList(table));
Privileges are passed as a Set, and each carries its condition. The List overloads of these two
methods are deprecated:
MetadataObject schema =
MetadataObjects.of(Lists.newArrayList("catalog1", "schema1"), MetadataObject.Type.SCHEMA);
client.grantPrivilegesToRole("schema_reader", schema, ImmutableSet.of(Privileges.SelectTable.allow()));
client.revokePrivilegesFromRole("schema_reader", schema, ImmutableSet.of(Privileges.SelectTable.deny()));