Repository navigation
Description
Search before asking
- I searched in the issues and found nothing similar.
Motivation
Java Paimon lets a REST catalog enforce row-level filters and column masks on reads through the table option query-auth.enabled (CoreOptions.QUERY_AUTH_ENABLED, default false):
- When a table with
query-auth.enabled=trueplans a scan,AbstractDataTableScan.authQuery()asks the catalog for the rules throughRESTCatalog.authTableQuery(), which sendsPOST /v1/{prefix}/databases/{database}/tables/{table}/authwith{"select": [...]}. The selected columns also include the columns referenced by the user filter and the partition filter, so their values are authorized before they are used for pruning. - The response is
{"filter": [...], "columnMasking": {...}}.filteris a list of JSON-serializedPredicates, combined with AND.columnMaskingmaps a column name to a JSON-serializedTransform(TableQueryAuthResult). - The splits are wrapped in
QueryAuthSplitwith the auth result, andAbstractDataTableRead.createReader()applies the row filter and the column masks when it reads them. With query auth enabled,withFilter()does not push the user predicate into the reader (it returns beforeinnerWithFilter()). - The rules are re-validated against the latest schema on every plan, so a schema change under a live scan fails closed (
validateAgainstSchema,validateReadableWithoutRename). - The option cannot be disabled through dynamic options once the table enables it (
AbstractFileStoreTable.checkImmutability). Search that a column mask would invalidate is rejected (TableQueryAuthResult.rejectSearchUnderQueryAuth). CatalogEnvironment.tableQueryAuth()skips the call when the table has no catalog loader, for example an external table of a REST catalog.
paimon-cpp implements none of this:
query-auth.enabledis not referenced anywhere underinclude/orsrc/;RestApiandResourcePathshave noauthendpoint, andCataloghas noauthTableQuery();- there is no deserialization of the Java
PredicateorTransformJSON.
As a result, a paimon-cpp reader that opens a query-auth.enabled=true table through RestCatalog reads every row and every column unmasked. The row filters and column masks the catalog defines for the table are not applied, while a Java reader of the same table gets the filtered, masked result.
Solution
Fail closed first, then add the feature.
-
Fail closed. Until the rules can be applied, reject scans and reads of a table with
query-auth.enabled=truewith a clearNotImplementedstatus, so the table can never be read without its rules. Also reject disabling the option through dynamic options, as Java does. -
REST protocol:
- add
ResourcePaths::AuthTable(database, table)->/v1/{prefix}/databases/{database}/tables/{table}/auth; - add
AuthTableQueryRequest(select, optional) andAuthTableQueryResponse(filter: list of strings,columnMasking: string map, both optional) torest_messages.h/.cpp; - add
RestApi::AuthTableQuery(identifier, select). It is a read and is safe to retry; - map the errors the same way Java does: 404 is table not found, 403 is no permission, 501 is not supported.
- add
-
Catalog: expose the call to the scan, for example as an internal interface next to
VersionManagedCatalogthatRestCatalogimplements. Reject system tables, and skip it for tables without catalog-managed versions (external tables), followingCatalogEnvironment.tableQueryAuth(). -
Rule parsing: deserialize the Java
PredicateJSON (leaf and compound predicates, literals of every supported type) and theTransformJSON used by column masks. Reject an empty column name, an empty transform, and JSONnull, asTableQueryAuthResultdoes. An unknown predicate or transform must fail the query instead of being ignored. -
Scan and read:
- at plan time, request the rules with the projected columns plus the columns referenced by the user filter and the partition filter;
- validate the rules against the latest schema;
- carry the result with the splits to the reader;
- in the reader, apply the row filter and the masks, and do not push the user predicate or column statistics of masked columns down;
- reject search that a mask would invalidate.
Tests:
rest_messages_test.cpp: request/response JSON round trips with absent fields;rest_catalog_test.cpp: the endpoint and error mapping, usingMockRestServer;- a read test: rows filtered, columns masked, the user filter not pushed down, fail closed when the rules cannot be parsed or reference unknown columns, and the option not being disabled through dynamic options.
Steps 1 and 2 can land on their own before the rest.
Anything else?
Compared against Java Paimon at apache/paimon@c2e001b7f2 and paimon-cpp at 41ed4ea.
Java references:
paimon-api/.../rest/RESTApi.java(authTableQuery);paimon-api/.../rest/ResourcePaths.java(authTable);paimon-api/.../rest/requests/AuthTableQueryRequest.java;paimon-api/.../rest/responses/AuthTableQueryResponse.java;paimon-core/.../rest/RESTCatalog.java(authTableQuery);paimon-core/.../catalog/TableQueryAuthResult.java;paimon-core/.../table/CatalogEnvironment.java(tableQueryAuth);paimon-core/.../table/source/AbstractDataTableScan.java(authQuery);paimon-core/.../table/source/AbstractDataTableRead.java;paimon-core/.../table/source/QueryAuthSplit.java.
Are you willing to submit a PR?
- I'm willing to submit a PR!
Activity
[-][Feature] Support query auth (row filters and column masking) for REST catalog tables[/-][+][Feature] Support query auth for REST catalog[/+]