|
| 1 | +--- |
| 2 | +title: "REST Management API" |
| 3 | +hide_table_of_contents: true |
| 4 | +--- |
| 5 | + |
| 6 | +<!-- |
| 7 | +Licensed to the Apache Software Foundation (ASF) under one or more |
| 8 | +contributor license agreements. See the NOTICE file distributed with |
| 9 | +this work for additional information regarding copyright ownership. |
| 10 | +The ASF licenses this file to You under the Apache License, Version 2.0 |
| 11 | +(the "License"); you may not use this file except in compliance with |
| 12 | +the License. You may obtain a copy of the License at |
| 13 | +
|
| 14 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 15 | +
|
| 16 | +Unless required by applicable law or agreed to in writing, software |
| 17 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 18 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 19 | +See the License for the specific language governing permissions and |
| 20 | +limitations under the License. |
| 21 | +--> |
| 22 | + |
| 23 | +The REST Management API is an experimental OpenAPI 3.1 control-plane extension for object |
| 24 | +privileges, row filters, and column masks in a Paimon REST Catalog. Its current contract version is |
| 25 | +`1.0` and may evolve incompatibly while the design is being validated. |
| 26 | + |
| 27 | +`RESTCatalog` exposes `permissionManagement()` and `policyManagement()` directly. These methods are |
| 28 | +intentionally not part of the generic `Catalog` interface. Other catalog implementations do not |
| 29 | +expose this management contract. |
| 30 | + |
| 31 | +## Catalog addressing |
| 32 | + |
| 33 | +All management endpoints use the opaque `prefix` returned by the REST Catalog config endpoint. It |
| 34 | +is not a catalog name in a payload and is independent of local engine catalog aliases. |
| 35 | + |
| 36 | +``` |
| 37 | +GET /v1/{prefix}/permissions |
| 38 | +POST /v1/{prefix}/permissions/grant |
| 39 | +POST /v1/{prefix}/permissions/revoke |
| 40 | +
|
| 41 | +GET /v1/{prefix}/databases/{database}/tables/{table}/policies |
| 42 | +POST /v1/{prefix}/databases/{database}/tables/{table}/policies |
| 43 | +POST /v1/{prefix}/databases/{database}/tables/{table}/policies/drop |
| 44 | +``` |
| 45 | + |
| 46 | +Policies are currently attached only to tables. The path is the attachment identity, so policy |
| 47 | +request bodies do not repeat a catalog, database, table, or resource type. Catalog- and |
| 48 | +database-level matching can be added later with explicit matching semantics instead of implied |
| 49 | +path inheritance. |
| 50 | + |
| 51 | +The complete wire contract is available in |
| 52 | +[`rest-management-open-api.yaml`](/rest-management-open-api.yaml). |
| 53 | + |
| 54 | +## Privileges and policies are independent |
| 55 | + |
| 56 | +A permission grants one access on one resource to one principal. A data policy restricts rows or |
| 57 | +columns visible through an already-authorized read. Creating a policy never grants `SELECT`, and |
| 58 | +revoking `SELECT` does not delete policies. |
| 59 | + |
| 60 | +This separation also defines the expected query path: |
| 61 | + |
| 62 | +1. The server evaluates object privileges. |
| 63 | +2. The server resolves all row-filter and column-masking policies applicable to the caller. |
| 64 | +3. The existing REST Catalog table authorization endpoint returns the stored Paimon predicate and |
| 65 | + column transforms to the engine. |
| 66 | +4. The engine applies those restrictions when planning the scan. |
| 67 | + |
| 68 | +Management payloads use the same serialized Paimon `Predicate` and `Transform` representation as |
| 69 | +the existing `AuthTableQueryResponse`. Policy conflict detection, schema validation, and principal |
| 70 | +resolution are server responsibilities. |
| 71 | + |
| 72 | +## Permission model |
| 73 | + |
| 74 | +Permission resources are structured objects: |
| 75 | + |
| 76 | +| Resource type | Required locator | Example | |
| 77 | +| --- | --- | --- | |
| 78 | +|`CATALOG`| none |`{"type":"CATALOG"}`| |
| 79 | +|`CATALOG_ALL`| none |`{"type":"CATALOG_ALL"}`| |
| 80 | +|`DATABASE`|`database`|`{"type":"DATABASE","database":"sales"}`| |
| 81 | +|`DATABASE_ALL`|`database`|`{"type":"DATABASE_ALL","database":"sales"}`| |
| 82 | +|`TABLE`|`database`, `table`|`{"type":"TABLE","database":"sales","table":"orders"}`| |
| 83 | +|`COLUMN`|`database`, `table`|`{"type":"COLUMN","database":"sales","table":"orders"}`| |
| 84 | +|`FUNCTION`|`database`, `function`|`{"type":"FUNCTION","database":"sales","function":"calculate_tax"}`| |
| 85 | +|`VIEW`|`database`, `view`|`{"type":"VIEW","database":"sales","view":"daily_orders"}`| |
| 86 | + |
| 87 | +Principals are opaque, canonical strings that are globally unique in the server namespace. Their |
| 88 | +format is server-defined and may encode a user, group, role, or service identity, for example |
| 89 | +`role:analyst` or an external identity-provider ARN. Principal type and membership resolution are |
| 90 | +server responsibilities. Access values are limited to 32 characters and principals to 128 |
| 91 | +characters. An implementation may resolve wire locators and principals to different stable |
| 92 | +persistence identifiers; those internal ids are not exposed by this API. |
| 93 | + |
| 94 | +The built-in accesses use a common data-authorization vocabulary. Creation accesses intentionally |
| 95 | +use their persisted names without underscores: |
| 96 | + |
| 97 | +| Access | Meaning | |
| 98 | +| --- | --- | |
| 99 | +|`ALL`| All accesses applicable to the resource. | |
| 100 | +|`CREATEDATABASE`| Create a database in a catalog. | |
| 101 | +|`DESCRIBE`| Read database metadata or select the current database. | |
| 102 | +|`ALTER`| Modify resource metadata. | |
| 103 | +|`DROP`| Drop the resource. | |
| 104 | +|`CREATETABLE`| Create a table in a database. | |
| 105 | +|`CREATEFUNCTION`| Create a function in a database. | |
| 106 | +|`CREATEVIEW`| Create a view in a database. | |
| 107 | +|`LIST`| List resources in a database. | |
| 108 | +|`SELECT`| Read table or view data, or use a function. | |
| 109 | +|`UPDATE`| Write table data, including insert, update, and delete operations. | |
| 110 | +|`GRANT`| Grant or revoke assignments on the resource. | |
| 111 | + |
| 112 | +Java helpers accept access names case-insensitively and normalize them before sending. The REST |
| 113 | +wire format uses upper case. Built-in accesses are resource-specific: |
| 114 | + |
| 115 | +| Resource | Accesses | |
| 116 | +| --- | --- | |
| 117 | +|`CATALOG`|`ALL`, `ALTER`, `DROP`, `GRANT`, `CREATEDATABASE`| |
| 118 | +|`CATALOG_ALL`|`ALL`, `DESCRIBE`, `ALTER`, `DROP`, `GRANT`, `CREATETABLE`, `CREATEVIEW`, `CREATEFUNCTION`, `LIST`, `SELECT`, `UPDATE`| |
| 119 | +|`DATABASE`|`ALL`, `DESCRIBE`, `ALTER`, `DROP`, `GRANT`, `CREATETABLE`, `CREATEVIEW`, `CREATEFUNCTION`, `LIST`| |
| 120 | +|`DATABASE_ALL`|`ALL`, `SELECT`, `UPDATE`, `ALTER`, `DROP`, `GRANT`| |
| 121 | +|`TABLE`|`ALL`, `SELECT`, `UPDATE`, `ALTER`, `DROP`, `GRANT`| |
| 122 | +|`COLUMN`|`SELECT`| |
| 123 | +|`VIEW`|`ALL`, `SELECT`, `ALTER`, `DROP`, `GRANT`| |
| 124 | +|`FUNCTION`|`ALL`, `SELECT`, `ALTER`, `DROP`, `GRANT`| |
| 125 | + |
| 126 | +An assignment identity is `resource`, `access`, and `principal`. Granting the same identity replaces |
| 127 | +its expiry, and revocation is idempotent. `CATALOG`, `DATABASE`, `TABLE`, `COLUMN`, `VIEW`, and |
| 128 | +`FUNCTION` apply only to the exact referenced resource. `CATALOG_ALL` is an explicit scope over the |
| 129 | +configured catalog's database, table, view, and function descendants; `DATABASE_ALL` is an explicit |
| 130 | +scope over the named database's table, view, and function descendants. These scope assignments also |
| 131 | +apply to descendants created later. They remain direct assignments in listing responses; the server |
| 132 | +does not synthesize inherited assignments. Resolving group membership and role inheritance remains a |
| 133 | +server responsibility. |
| 134 | + |
| 135 | +### Column permissions |
| 136 | + |
| 137 | +A column permission uses a `COLUMN` resource whose locator is the containing table, `SELECT` |
| 138 | +access, and one `columns` object. Exactly one non-empty list is allowed: |
| 139 | + |
| 140 | +-`columnNames` is an allowlist. Only the named top-level columns are readable. |
| 141 | +-`excludedColumnNames` is a denylist. Every current top-level column except the named columns is |
| 142 | + readable. |
| 143 | + |
| 144 | +For example, this assignment allows only `order_id` and `region`: |
| 145 | + |
| 146 | +```json |
| 147 | +{ |
| 148 | +"resource": { |
| 149 | +"type": "COLUMN", |
| 150 | +"database": "sales", |
| 151 | +"table": "orders" |
| 152 | + }, |
| 153 | +"access": "SELECT", |
| 154 | +"principal": "role:analyst", |
| 155 | +"columns": { |
| 156 | +"columnNames": ["order_id", "region"] |
| 157 | + } |
| 158 | +} |
| 159 | +``` |
| 160 | + |
| 161 | +The assignment identity remains `(resource, access, principal)`; `columns` is not part of the |
| 162 | +identity. Granting the same identity replaces the entire previous allowlist or denylist rather than |
| 163 | +merging individual names. Revocation therefore omits `columns` and removes the whole column |
| 164 | +assignment. |
| 165 | + |
| 166 | +All named columns must exist when granted, and the table must enforce query authorization. A server |
| 167 | +may enable `query-auth.enabled` atomically with the grant; otherwise it must reject the grant. Column |
| 168 | +names refer only to top-level fields. For every effective caller principal, applicable column ranges |
| 169 | +are intersected. If any applicable range rejects a selected column, the query fails rather than |
| 170 | +silently dropping that column. |
| 171 | + |
| 172 | +Schema evolution keeps the assignment attached to the stable table identity. Renaming a referenced |
| 173 | +column updates its stored name. Dropping a referenced column removes it from the range; if that |
| 174 | +would leave the stored list empty, the assignment is removed. An allowlist denies columns added |
| 175 | +later, while a denylist allows them, so allowlists are safer when new columns may contain sensitive |
| 176 | +data. |
| 177 | + |
| 178 | +`expireTime`, when present, is an exclusive upper bound evaluated against the REST server clock. |
| 179 | +At `now >= expireTime`, the assignment must not authorize access. Expired direct assignments may |
| 180 | +remain visible in listings until server cleanup. Timestamps must not be more precise than |
| 181 | +milliseconds; the wire value uses UTC `Z` and contains at most three fractional digits. |
| 182 | + |
| 183 | +Resource objects in this API are wire locators, not persistence identities. Servers must bind direct |
| 184 | +assignments to a stable internal resource identity: renaming a database, table, function, or view |
| 185 | +retains its assignments and subsequent responses use the new locator; dropping it removes its direct |
| 186 | +assignments; recreating the same locator does not restore them. |
| 187 | + |
| 188 | +## Data policy model |
| 189 | + |
| 190 | +A data policy is attached directly to one table and one principal. It applies whenever that |
| 191 | +principal is effective for the caller after the server resolves group and role membership. A |
| 192 | +principal can have at most one row filter on a table and at most one column mask on each table |
| 193 | +column. A row-filter identity is `(table, ROW_FILTER, principal)`; a column-mask identity is |
| 194 | +`(table, COLUMN_MASKING, principal, onColumn)`. |
| 195 | + |
| 196 | +Each policy contains exactly one typed definition: |
| 197 | + |
| 198 | +| Definition | Required fields | Result | |
| 199 | +| --- | --- | --- | |
| 200 | +|`rowFilter`|`predicate`| One serialized Paimon `Predicate`, applied to every scan. | |
| 201 | +|`columnMask`|`onColumn`, `transform`| One serialized Paimon `Transform` whose result replaces the protected column. | |
| 202 | + |
| 203 | +The common field is one `principal`. `rowFilter.predicate` maps directly to one entry in |
| 204 | +`AuthTableQueryResponse.filter`. `columnMask.onColumn` and `columnMask.transform` map directly to |
| 205 | +one key and value in `AuthTableQueryResponse.columnMasking`. Each JSON value is limited to 60 KiB |
| 206 | +in UTF-8. This is Paimon's versioned serialization format rather than SQL text or a portable policy |
| 207 | +DSL; clients and servers must use compatible Paimon versions. |
| 208 | + |
| 209 | +Policy creation must be rejected unless all of these conditions hold: |
| 210 | + |
| 211 | +1. The target database and table exist. |
| 212 | +2. The table has `query-auth.enabled=true`; otherwise a stored policy could be silently bypassed. |
| 213 | +3. The referenced principal exists. |
| 214 | +4. The predicate or transform is recognized by the server, deserializes to a non-null Paimon |
| 215 | + object, and is canonicalized before storage. |
| 216 | +5. Every referenced field and `onColumn` exists in the target table, and a transform's output type |
| 217 | + matches its protected column. |
| 218 | + |
| 219 | +These invariants continue to apply for the whole table lifecycle. Servers must bind policies to a |
| 220 | +stable table identity, preserve that binding across table renames, and remove the policies when the |
| 221 | +table is dropped. A table with policies must reject changes that disable `query-auth.enabled` or |
| 222 | +remove or rename a protected or referenced column, unless the policy update and schema change are |
| 223 | +performed atomically. If an implementation persists all masks for one principal in one document, |
| 224 | +creating or dropping one column mask must atomically preserve masks for other columns. |
| 225 | + |
| 226 | +At authorization time, all applicable row filters must be combined with logical `AND`. More than |
| 227 | +one applicable column mask targeting the same column must fail closed. An invalid, unsupported, or |
| 228 | +schema-incompatible predicate or transform must also fail closed rather than omit a restriction. |
| 229 | + |
| 230 | +This experimental contract deliberately does not define governed tags, catalog/database policy |
| 231 | +inheritance, or tag-driven matching. Those features need explicit match conditions and conflict |
| 232 | +rules before being added. |
0 commit comments