roryqi commented on code in PR #11790:
URL: https://github.com/apache/gravitino/pull/11790#discussion_r3489895489


##########
design-docs/gravitino-role-assumption.md:
##########
@@ -0,0 +1,334 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+
+# Design: Role Assumption (SET ROLE) — Narrowing Effective Permissions
+
+| Field | Value |
+| ---------- | 
------------------------------------------------------------------------- |
+| Status     | Draft |
+| Authors    | @bharos |
+| Created    | 2026-06-24 |
+| Discussion | [#10894](https://github.com/apache/gravitino/discussions/10894) 
|
+| Scope      | Native authorization path (Iceberg REST catalog + native 
Gravitino API) |
+
+---
+
+## 1. Summary
+
+When a user holds multiple roles, Gravitino always enforces the union of all 
of them. A workload has no
+way to run with a narrower subset of the access its identity could reach.
+
+This document proposes **role assumption** — the analog of Snowflake's `USE 
ROLE` and Hive's `SET ROLE`.
+A caller declares which role(s) should be active for a request, Gravitino 
verifies the caller actually
+holds them, and authorization is evaluated against only that active set. The 
feature can only *reduce* a
+caller's effective permissions, never expand them. If no role is declared, 
behavior is exactly as it is
+today.
+
+The mechanism is intentionally simple: a request header, 
`X-Gravitino-Active-Role`, carries the active
+role(s), and the server narrows enforcement to match — across access checks, 
list results, and credential
+vending alike. Section 3 defines the header and its values; Section 4 explains 
how the server enforces
+it. Transport is the easy part — the common instinct is to treat this as "just 
a Trino change," but the
+substance is server-side.
+
+---
+
+## 2. Motivation
+
+### 2.1 The problem
+
+A pipeline or AI agent that only needs 5 tables still runs with access to 
every table (say 105) its
+identity can reach. If that workload misbehaves — a logic bug, a bad query, a 
leaked credential, or an
+LLM agent over-reaching — the blast radius is everything the identity could 
touch, and out-of-scope
+access **succeeds silently** instead of being denied and surfaced.
+
+### 2.2 Why it matters now
+
+A few things make this worth doing now. It gives workloads real runtime 
least-privilege: a job can drop
+to exactly the access it needs without anyone having to mint a separate 
narrowly-scoped identity for it.
+It also produces a much cleaner audit signal — with narrowing on, out-of-scope 
access turns into a hard
+deny you can alert on, instead of a successful-but-unexpected access buried in 
the logs. AI agents are
+the sharpest version of the same need: you want an agent to operate inside a 
declared, minimal scope and
+to fail the moment it steps outside it. There's also a concrete migration 
angle — Hive's SQL Standard
+Authorization supports `SET ROLE`, so teams moving HMS tables to Iceberg 
behind Gravitino lose that
+capability today, and this restores it.
+
+### 2.3 Goals
+
+- Let a caller narrow the active role set for the native authorization path 
(Iceberg REST + native
+  API).
+- Guarantee narrowing is **subtractive only** — it can never grant access the 
caller lacks.
+- Apply the narrowing consistently everywhere authorization is consulted: 
direct access checks **and**
+  list-result filtering **and** credential vending.
+- Be **fully backward compatible**: no declared role ⇒ today's union behavior, 
byte-for-byte.
+
+### 2.4 Non-goals (initial)
+
+- Dynamic in-session `SET ROLE` switching mid-connection (deferred to a later 
phase — see Section 11).
+- Narrowing for **pushdown-authorized** catalogs (Hive/JDBC via Ranger/JDBC 
plugins), where
+  enforcement happens in the external system — see Section 6.
+- Changing how ownership is modeled (its *interaction* with narrowing is an 
explicit open decision —
+  see Section 5).
+- Write/`CREATE` semantics (which role owns newly created objects) — flagged 
for forward-compat only.
+
+---
+
+## 3. The active-role header
+
+The interface is a single request header:
+
+```
+X-Gravitino-Active-Role: <value>
+```
+
+The caller sets it to declare which of its roles should be active for that 
request. The server validates
+the value against the roles the caller actually holds, then evaluates 
authorization against only those
+roles.
+
+### 3.1 Accepted values
+
+A value is either a role name, a comma-separated list of role names, or one of 
the reserved keywords
+`ALL` / `NONE`:
+
+| Value | Meaning |
+|---|---|
+| `<role>` (e.g. `analyst`) | Activate a single named role. |
+| `<role>,<role>` (e.g. `analyst,reader`) | Activate a list of roles; 
effective access is the union of just these. |
+| `ALL` | Activate every role the caller holds — identical to today's 
behavior. |
+| `NONE` | Activate no roles; all role-derived access is denied. |
+| *(header absent)* | Same as `ALL` — fully backward compatible. |
+
+Here `analyst` and `reader` are example role names, not keywords; only `ALL` 
and `NONE` are reserved.
+This mirrors the vocabulary users already know from Hive (`SET ROLE role | ALL 
| NONE`) and Snowflake
+(`USE SECONDARY ROLES ALL | NONE`).
+
+### 3.2 Examples
+
+A reporting job that should only ever read through its `analyst` role:
+
+```
+GET /iceberg/v1/namespaces/sales/tables
+X-Gravitino-Active-Role: analyst
+```
+
+Declaring a role the caller does not actually hold is rejected — this is what 
keeps the feature
+subtractive:
+
+```
+X-Gravitino-Active-Role: admin     →  403 Forbidden   (caller is not a member 
of admin)
+```
+
+### 3.3 Semantics
+
+- **Subtractive only.** The server validates membership first, so the header 
can never grant access the
+  caller lacks — at most it removes roles from the evaluated set.
+- **Consistent everywhere.** The narrowed set applies to every authorization 
decision in the request:
+  direct access checks, the filtering of list results, and the privileges used 
for credential vending.
+- **Backward compatible.** No header (or `ALL`) means today's union behavior, 
unchanged.
+
+---
+
+## 4. How the server enforces narrowing
+
+Without an `X-Gravitino-Active-Role` header, Gravitino evaluates each 
operation against the user's full
+set of roles — those granted directly plus those inherited from groups. The 
header narrows that set for
+the request:
+
+1. **Resolve** the caller's effective roles the normal way — direct grants 
plus group-inherited ones.
+   Say that's `{analyst, editor, auditor}`.
+2. **Validate** the header against that set. The declared role must be one the 
caller actually holds; a
+   role they don't hold is rejected. This is the guardrail that keeps 
narrowing *subtractive* — you can
+   reduce what you use, never assume a role you were never granted.
+3. **Enforce** using only the validated active role(s). With 
`X-Gravitino-Active-Role: analyst`, the
+   request is evaluated as `analyst` alone; `editor` and `auditor` are simply 
not consulted.
+
+So the active set is `(roles named in the header) ∩ (the caller's effective 
roles)` — always a subset,
+never a way to widen or switch into a role you don't hold. Because it keys on 
the role and not how the
+role was acquired, a role held only through a group works exactly the same — 
group-based authorization is
+covered with nothing extra.
+
+This needs **no change to the Casbin model**. Authorization is already checked 
with the user as the
+subject, and a grouping rule expands the user to all their roles — that 
expansion is the union. Since
+policies are written against *roles*, the same enforcer is instead called with 
each active *role* as the
+subject. Narrowing is just that change of subject, and deny rules run over the 
same active subset, so
+deny-wins is preserved — an explicit deny on an active role still denies.
+
+The one identity-derived exception is **ownership**: access a user (or their 
group) gets from *owning* an
+object comes from the ownership grant, not a role, so under the proposed 
default it still applies when
+roles are narrowed. Whether narrowing should also suppress owner access is the 
open decision in Section 5.
+
+Two implementation properties keep this correct. Narrowing is applied by 
*choosing which roles to
+evaluate* for the request — never by editing the user's bindings, since the 
enforcers are shared
+process-wide across all users. And it must reach **every** decision: list 
filtering and credential
+vending go through the same check as direct access, so a narrowed caller lists 
only what its active roles
+allow and is never vended a broader storage credential — provided vending is 
wired in deliberately, or
+the narrowing could be bypassed through the storage token.
+
+---
+
+## 5. Ownership — the key open decision
+
+There is one place where narrowing is not automatic, and it needs a decision 
from the community. In
+Gravitino, object **ownership** grants access directly to the owning user or 
group, independent of
+roles. Because most operations are allowed if the caller either holds a 
granting role *or* owns the
+object, an owner stays authorized no matter which roles are active.
+
+So narrowing roles does not, on its own, narrow the access a caller derives 
from ownership. This is the
+one spot where Gravitino differs from Snowflake, where ownership flows through 
the active (primary)
+role. The community needs to choose the semantics:
+
+- **Option A — ownership always applies.** Narrowing affects role-granted 
privileges only; you keep
+  access to objects you own. Simplest and least surprising for existing 
deployments, but a narrowed
+  workload could still reach objects it owns outside its declared scope.
+- **Option B — ownership is narrowed too.** When an active set is declared, 
ownership-derived access is
+  honored only if it is reachable through the active roles. A stronger 
least-privilege guarantee, but a
+  larger behavioral change.
+- **Option C — make it explicit.** A reserved value in the header grammar lets 
the caller opt ownership
+  in or out, mirroring Snowflake's primary-vs-secondary distinction.
+
+Proposed default: ship **Option A** for v1 — it delivers the read-narrowing 
that motivates the feature

Review Comment:
   OK for me.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to