This is an automated email from the ASF dual-hosted git repository.

bharos pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new 43844e1c8a [#11968] docs(auth): document the X-Gravitino-Active-Roles 
header (#12365)
43844e1c8a is described below

commit 43844e1c8af2c51650861193d136da511c61f3cf
Author: Bharath Krishna <[email protected]>
AuthorDate: Wed Aug 5 18:52:12 2026 -0700

    [#11968] docs(auth): document the X-Gravitino-Active-Roles header (#12365)
    
    ### What changes were proposed in this pull request?
    
    Documents the `X-Gravitino-Active-Roles` header, which shipped across
    #11966, #11967, and #12095 with no user-facing documentation.
    
    Adds an **Active Roles** section to `docs/security/access-control.md`
    covering:
    
    - the grammar — a role name, a comma-separated list, `ALL`, `NONE`, and
    an absent or empty value meaning `ALL`;
    - the matching rules — names are exact, `ALL` and `NONE` are recognized
    only in upper case, whitespace is trimmed and duplicates collapse;
    - what narrowing changes — it only ever subtracts, `DENY` stays global,
    ownership is untouched, and the narrowed set reaches direct checks, list
    filtering, and credential vending alike;
    - the two error classes, `400` for a malformed value and `403` for a
    role the caller does not hold, and why an unheld role is rejected rather
    than ignored;
    - how Spark, Trino 481+, and the Java client send the header, and that
    the two engine settings are catalog-level and static;
    - the scope limit to Gravitino's native authorization path, with a
    pointer to Authorization Pushdown.
    
    Also adds a short **Read or Write Scope** note to
    `docs/security/credential-vending.md`, since a narrowed caller that no
    longer holds `MODIFY_TABLE` is vended a read-only credential.
    
    The wording follows the implementation rather than the design document
    in one place: an unrecognized keyword is parsed as an ordinary role name
    and therefore returns `403`, not `400`.
    
    ### Why are the changes needed?
    
    The feature is complete and enforced on the native and Iceberg REST
    paths, but `X-Gravitino-Active-Roles` did not appear anywhere under
    `docs/`, so it was undiscoverable to users.
    
    Part of #11968
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, documentation only. No behavior changes.
    
    ### How was this patch tested?
    
    Documentation only, no tests. Verified the Markdown renders correctly
    and that the relative links and the `#active-roles` anchor resolve.
    Every documented rule was checked against the implementation in
    `ActiveRolesParser`, `AuthenticationFilter`,
    `GravitinoInterceptionService`, and `IcebergTableOperationExecutor`.
---
 docs/security/access-control.md     | 79 +++++++++++++++++++++++++++++++++++++
 docs/security/credential-vending.md |  4 ++
 2 files changed, 83 insertions(+)

diff --git a/docs/security/access-control.md b/docs/security/access-control.md
index 538a80abba..c49595a3b2 100755
--- a/docs/security/access-control.md
+++ b/docs/security/access-control.md
@@ -274,6 +274,85 @@ Granting or revoking a privilege on an object takes 
`MANAGE_GRANTS` on that obje
 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.
+
+```text
+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.
+
+- **`DENY` stays 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_TABLE` is 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:
+
+```properties
+spark.sql.catalog.{catalog}.header.X-Gravitino-Active-Roles = analyst
+```
+
+Trino 481 and later forwards headers configured on the catalog:
+
+```properties
+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:
+
+```java
+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](authorization-pushdown.md).
+
 ## Server Configuration
 
 Settings live in `${GRAVITINO_HOME}/conf/gravitino.conf`. Authorization is off 
by default; the
diff --git a/docs/security/credential-vending.md 
b/docs/security/credential-vending.md
index 8620b50883..a8277cfa95 100755
--- a/docs/security/credential-vending.md
+++ b/docs/security/credential-vending.md
@@ -364,6 +364,10 @@ GET 
/api/metalakes/{metalake}/objects/catalog/{catalog}/credentials
 
 The Gravitino Spark and Flink connectors call this for you and inject the 
returned credentials, so no client configuration is needed.
 
+### Read or Write Scope
+
+Over the IRC, a credential is vended for writing when the caller is entitled 
to modify the table, and for reading otherwise. Narrowing the caller's roles 
with the `X-Gravitino-Active-Roles` header narrows this as well, so a caller 
whose active roles no longer carry `MODIFY_TABLE` is vended a read-only 
credential. See [Narrowing Access with Active 
Roles](access-control.md#narrowing-access-with-active-roles).
+
 ## Custom Credentials
 
 Gravitino supports custom credentials. You can implement the 
`org.apache.gravitino.credential.CredentialProvider` interface to support 
custom credentials, and place the corresponding jar in the classpath of the IRC 
or the Fileset catalog.

Reply via email to