Skip to main content

Access Control

Overview

Apache Gravitino federates the catalogs of many systems under a single metalake, so permissions are defined once there rather than separately in each system. When authorization is enabled, the server checks every request before the operation runs and rejects it if the caller is not entitled to it.

Two things decide the answer:

  • Ownership comes with creation. Whoever creates an object owns it, and owning it carries the right to alter it, drop it, and hand it to someone else. Ownership reaches down, so owning a catalog means administrative control over the schemas and tables inside it.
  • Privileges are named permissions, each authorizing one kind of operation: SELECT_TABLE reads a table, CREATE_SCHEMA creates a schema in a catalog. A privilege is never given to a person directly. Privileges are collected into a role, and the role is granted to users and to groups.

Three rules govern how those apply:

  • Grants reach downward. A grant covers everything beneath the object it is made on, both what exists now and what is created later. SELECT_TABLE on a schema covers every table in it.
  • Nothing is permitted unless granted. A user added to a metalake and given nothing can see the metalake and nothing else.
  • An explicit deny overrides everything. Each privilege in a role carries an ALLOW or a DENY condition, and a DENY beats an ALLOW held in any other role and at any other level of the hierarchy. A denial cannot be undone by granting something elsewhere, which makes DENY the way to carve one object out of a broad grant.

Roles, users, and groups each have a page of their own: Roles covers the privileges and what they allow, Users covers service administrators and membership, and Groups covers where group membership comes from.

Quick Start

Authorization is off by default. Turn it on in ${GRAVITINO_HOME}/conf/gravitino.conf, name at least one service administrator, and restart the server:

gravitino.authorization.enable = true
gravitino.authorization.serviceAdmins = {admin_user}

Service administrators are the only users who can create metalakes, and everything after that is done through the API. The Walkthrough runs a full sequence end to end, from an empty server to a user with read access to one schema. Server Configuration covers the remaining settings, including how a caller's identity reaches the server.

Authorization Model

Principals and Objects

Users and Groups

Users and groups are the principals that privileges and ownership are assigned to. A user must be added to a metalake before it can do anything there, and a role granted to a group applies to every member. See Users and Groups.

Objects

Everything Gravitino manages is an object with a type and a name. The name is the dotted path to it below the metalake, so a table is {catalog}.{schema}.{table}, and requests identify an object by both type and name, since the same name can exist at more than one type.

Access to an object is controlled by privileges, granted through roles, and by ownership. Ownership behaves like a privilege that arrives with the object rather than one you grant, and it carries the administrative rights, altering, dropping, and transferring, that no privilege name covers.

Everything sits under a metalake, but only the data objects nest below a catalog:

Metalake (top level)
├── Catalog (represents a data source)
│ └── Schema
│ ├── Table
│ ├── View
│ ├── Topic
│ ├── Fileset
│ ├── Model
│ └── Function
├── Tag
├── Policy
├── Job Template
├── Role
└── Job

Three things about that tree are worth noting:

  • Roles and jobs are controlled by ownership alone, since no privilege binds to them, though CREATE_ROLE and RUN_JOB on the metalake gate creating them.
  • Columns do not appear at all. They are reached through their table and carry no controls of their own, so there is no column-level grant in this model.
  • Users and groups are not objects. They are the principals that privileges and ownership are assigned to.

Grants

Privileges

A privilege authorizes a specific operation on an object, for example SELECT_TABLE or CREATE_SCHEMA. Privileges are added to roles, and roles are granted to users and groups. Privileges are never granted directly to a user.

A role is a named set of privileges granted to users and groups. See Roles.

Ownership

Ownership can be held by a group as well as a user, in which case every member of that group holds it, and it can be transferred at any time. It applies to metalakes, catalogs, schemas, tables, views, topics, filesets, models, functions, roles, tags, policies, job templates, and jobs.

Resolution

Evaluating a Request

Every authorized endpoint declares the conditions under which a caller may invoke it, and the check passes if any one of them holds. Loading a table, for example, succeeds when:

  • The caller owns the metalake or the catalog.
  • The caller owns the schema and holds USE_CATALOG.
  • The caller holds both USE_CATALOG and USE_SCHEMA, and additionally owns the table or holds SELECT_TABLE or MODIFY_TABLE.

Note the third case. Granting SELECT_TABLE on a schema covers every table in that schema, but on its own it authorizes nothing, because the traversal privileges are still missing. Owning the table is the same: an owner who cannot reach the catalog and schema above it cannot load the table, though they can still read its owner and transfer it.

A failed check returns 403 Forbidden. Some read paths return 404 Not Found instead, so that a caller cannot infer the existence of an object they are not entitled to see. List operations do not fail; they return only the entries the caller is entitled to see.

Allow and Deny

DENY always wins. It beats an ALLOW in the same role, an ALLOW from any other role the user holds, and an ALLOW at any other level of the hierarchy, in either direction: a DENY on a catalog survives an ALLOW on its metalake, and a DENY on a metalake survives an ALLOW on its catalog. So a denial cannot be circumvented by granting something elsewhere.

Sibling privileges are independent of each other. DENY MODIFY_TABLE leaves ALLOW SELECT_TABLE intact, and the same holds for PRODUCE_TOPIC and CONSUME_TOPIC, and for READ_FILESET and WRITE_FILESET. To withhold both read and write, deny both.

Server Configuration

Settings live in ${GRAVITINO_HOME}/conf/gravitino.conf. Authorization is off by default; the Quick Start shows the two settings that turn it on.

Setting *DescriptionDefault
enableEnable or disable authorizationfalse
serviceAdminsComma-separated service administrator usernames. Required when enable is true(none)
implMetadata authorization implementation
threadPoolSizeThread pool size for metadata authorization requests10
jcasbin.cacheExpirationSecsHow long a cache entry stays valid. Lowering it reduces staleness and increases backend reads3600
jcasbin.roleCacheSizeMaximum size of each role-related cache. Applied to three caches, so real memory use is about 3x this value10000
jcasbin.ownerCacheSizeMaximum size of the owner cache100000
jcasbin.metadataIdCacheSizeMaximum size of the metadata name-to-ID cache100000
jcasbin.changePollIntervalSecsHow often the server polls for entity and owner changes to invalidate its caches. Must be greater than zero3

* Setting names omit the leading gravitino.authorization. prefix. Write it out in full in the configuration file:

gravitino.authorization.jcasbin.roleCacheSize = 10000

† The default is org.apache.gravitino.server.authorization.jcasbin.JcasbinAuthorizer.

Setting gravitino.authorization.impl to org.apache.gravitino.server.authorization.PassThroughAuthorizer runs the server with authorization enabled but every check bypassed. Pass-through mode exists for migration and is not intended for production. See Enabling Authorization on Existing Metalakes.

The default authorizer keeps role and ownership information in Caffeine caches, so most authorization decisions need no backend read. When privileges or ownership change through the Gravitino API, the server handling the change invalidates the affected entries immediately. Other nodes in a multi-node deployment pick the change up on their next poll, so a revocation can take up to jcasbin.changePollIntervalSecs to take effect across the cluster. Lower the interval if that window is unacceptable for your environment.

Authentication

Authorization decides what a caller may do; authentication establishes who the caller is. gravitino.authenticators selects the mechanism, and defaults to simple, which reads an unvalidated HTTP Basic header and suits local evaluation only. For OAuth:

gravitino.authenticators = oauth
gravitino.authenticator.oauth.jwksUri = {jwks_uri}
gravitino.authenticator.oauth.serviceAudience = {audience}

# The JWT claims that become the Gravitino user name and group memberships
gravitino.authenticator.oauth.principalFields = preferred_username
gravitino.authenticator.oauth.groupsFields = groups

Two of those settings connect a token to this page:

  • principalFields names the JWT claim whose value becomes the Gravitino user name, so it must produce the same strings you add to metalakes and grant roles to. It defaults to sub, which is usually an opaque provider ID rather than a name anyone would type.
  • groupsFields names the claim supplying group membership, which is how a role granted to a group reaches a user.

See How to Authenticate for Kerberos and the other options.

Behavior Notes

  • Add users to a metalake before creating metadata objects in it.
  • If a request carries no user identity, the operation runs as the anonymous user.
  • When authorization is enabled, the creator of a metalake is automatically added to it as a user.

Enabling Authorization on Existing Metalakes

Metalakes created while gravitino.authorization.enable was false have no owner. Once full authorization is enabled, operations on an ownerless metalake fail, so assign owners first.

Step 1. Enable authorization in pass-through mode, which turns on the authorization machinery while bypassing the checks:

# Turn authorization on, but bypass every check while you assign owners
gravitino.authorization.enable = true
gravitino.authorization.serviceAdmins = {admin_user_1},{admin_user_2}
gravitino.authorization.impl = org.apache.gravitino.server.authorization.PassThroughAuthorizer

Restart the server.

Step 2. Set an owner for each existing metalake.

curl -X PUT \
"$GRAVITINO/owners/metalake/{metalake}" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Accept: application/vnd.gravitino.v1+json" \
-H "Content-Type: application/json" \
-d '{
"name": "{admin_user_1}",
"type": "USER"
}'

Step 3. Remove the gravitino.authorization.impl line so that the default authorizer takes over:

# The same settings with the pass-through line gone, so checks now apply
gravitino.authorization.enable = true
gravitino.authorization.serviceAdmins = {admin_user_1},{admin_user_2}

Step 4. Restart the server.

Confirm that every metalake has an owner before completing step 3. Any metalake left without one

Walkthrough

Three identities act in turn, from an empty server to a user reading one schema. The service administrator bootstraps the metalake and hands it off, manager runs it, and staff builds and shares the data. Each presents its own bearer token.

Every call sends the same two headers, so the examples use a helper:

GRAVITINO=http://localhost:8090/api/metalakes/{metalake}

gravitino() {
curl -sS -H "Accept: application/vnd.gravitino.v1+json" \
-H "Content-Type: application/json" "$@"
}

1. The service administrator creates the metalake and hands it over. Creating it adds the creator as a user and makes them owner, which is what authorizes the next two calls. After the transfer, manager owns the metalake and the service administrator has no further part to play.

gravitino -X POST "http://localhost:8090/api/metalakes" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{"name": "{metalake}", "comment": "example metalake", "properties": {}}'

gravitino -X POST "$GRAVITINO/users" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{"name": "manager"}'

gravitino -X PUT "$GRAVITINO/owners/metalake/{metalake}" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{"name": "manager", "type": "USER"}'

2. manager delegates catalog creation to staff. Owning the metalake lets manager add users and create roles without any grant. The role carries CREATE_CATALOG on the metalake, so it covers every catalog staff creates, now and later.

gravitino -X POST "$GRAVITINO/users" \
-H "Authorization: Bearer $MANAGER_TOKEN" \
-d '{"name": "staff"}'

gravitino -X POST "$GRAVITINO/roles" \
-H "Authorization: Bearer $MANAGER_TOKEN" \
-d '{
"name": "catalog_manager",
"properties": {},
"securableObjects": [
{
"fullName": "{metalake}",
"type": "METALAKE",
"privileges": [{"name": "CREATE_CATALOG", "condition": "ALLOW"}]
}
]
}'

gravitino -X PUT "$GRAVITINO/permissions/users/staff/grant" \
-H "Authorization: Bearer $MANAGER_TOKEN" \
-d '{"roleNames": ["catalog_manager"]}'

3. staff builds out the data. Creating the catalog makes staff its owner, and that ownership carries everything inside it, so the schema needs no further grant. The example uses Hive; any provider works, with its own properties.

gravitino -X POST "$GRAVITINO/catalogs" \
-H "Authorization: Bearer $STAFF_TOKEN" \
-d '{
"name": "{catalog}",
"type": "RELATIONAL",
"provider": "hive",
"properties": {"metastore.uris": "thrift://{hive_host}:9083"}
}'

gravitino -X POST "$GRAVITINO/catalogs/{catalog}/schemas" \
-H "Authorization: Bearer $STAFF_TOKEN" \
-d '{"name": "{schema}"}'

4. staff gives analyst read access. 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. analyst must already be a user in the metalake.

gravitino -X POST "$GRAVITINO/roles" \
-H "Authorization: Bearer $STAFF_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"}
]
}
]
}'

gravitino -X PUT "$GRAVITINO/permissions/users/analyst/grant" \
-H "Authorization: Bearer $STAFF_TOKEN" \
-d '{"roleNames": ["schema_reader"]}'

analyst can now read every table in {catalog}.{schema}, including tables created there later, and can do nothing anywhere else in the metalake. Queries through the Gravitino connector are authorized against the same ownership and privileges that governed the metadata calls above.

Ownership Endpoints

Paths are relative to https://{gravitino_host}/api/metalakes/{metalake}. For request and response schemas, see the Gravitino REST API.

OperationMethodPath
Get or set an object's ownerGET, PUT/owners/{object_type}/{object_name}

In the Java client, an owner is set with an Owner.Type, not a string:

client.setOwner(schema, "analyst", Owner.Type.USER);