Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@hantangwangd
Copy link
Member

@hantangwangd hantangwangd commented Dec 14, 2025

Description

This PR implements the following functionality:

  1. Add basic access control check for calling procedures. Procedures are now access control objects at the same level as tables, requiring EXECUTE privilege for invocation.

  2. Make access control in Iceberg configurable and introduces file-based access control into Iceberg. The default remains allow-all.

  3. Add target table's INSERT/DELETE access control checks for TABLE_DATA_REWRITE distributed procedure.

  4. Add row filters and column masks access control checks for TABLE_DATA_REWRITE distributed procedure.

To be implemented: Enforce access control for CALL procedures in RangerBasedAccessControl and SqlStandardAccessControl as well. (current behavior: calls are always allowed, but the necessary permissions on target tables will be checked.)

Motivation and Context

See issue: #26680

Impact

  • Access control in Iceberg is now configuration. The currently supported types are allow-all and file.
  • Support access control for procedures, including the access control for the target tables involved in distributed procedures

Test Plan

  • Newly added test cases to cover the access control for procedures and distributed procedures in Iceberg

Contributor checklist

  • Please make sure your submission complies with our contributing guide, in particular code style and commit standards.
  • PR description addresses the issue accurately and concisely. If the change is non-trivial, a GitHub Issue is referenced.
  • Documented new properties (with its default value), SQL syntax, functions, or other functionality.
  • If release notes are required, they follow the release notes guidelines.
  • Adequate tests were added if applicable.
  • CI passed.
  • If adding new dependencies, verified they have an OpenSSF Scorecard score of 5.0 or higher (or obtained explicit TSC approval for lower scores).

Release Notes

== RELEASE NOTES ==

Security Changes
 * Add support for procedure calls in access control.
 * Add fine-grained access control for procedure calls in the file-based access control system.
 * Add a temporary configuration property ``hive.restrict-procedure-call`` for ranger and sql-standard access control. It defaults to ``true``, meaning procedure calls are restricted.
   To allow procedure calls, set this configuration property to ``false``.

Iceberg Connector Changes
 * Add support for configuring access control in Iceberg using the ``iceberg.security`` property in the Iceberg catalog properties file. The supported types are ``allow-all`` and ``file``.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 14, 2025

Reviewer's Guide

Introduces EXECUTE-level access control for procedures (including Iceberg distributed procedures), wires Iceberg to a configurable security system (allow-all or file-based), and enforces additional checks such as table INSERT/DELETE permissions and row filter/column mask constraints while updating SPI/System/connector access control surfaces and tests.

Sequence diagram for procedure CALL access control

sequenceDiagram
    actor User
    participant Client
    participant Coordinator
    participant StatementAnalyzer
    participant CallTask
    participant AccessControlManager as AccessControl
    participant SystemAC as SystemAccessControl
    participant ConnectorAC as ConnectorAccessControl

    User->>Client: submit CALL schema.proc(...)
    Client->>Coordinator: send query
    Coordinator->>StatementAnalyzer: analyze Call
    StatementAnalyzer->>AccessControlManager: checkCanCallProcedure(txnId, identity, ctx, procedureName)
    AccessControlManager->>SystemAC: checkCanCallProcedure(identity, ctx, catalogSchemaTableName)
    SystemAC-->>AccessControlManager: allow or deny (system-level)
    alt catalog has ConnectorAccessControl
        AccessControlManager->>ConnectorAC: checkCanCallProcedure(connectorTxn, connectorIdentity, ctx, schemaTableName)
        ConnectorAC-->>AccessControlManager: allow or deny (connector-level)
    end
    AccessControlManager-->>StatementAnalyzer: result
    StatementAnalyzer-->>Coordinator: analysis (distributed or regular)
    note over StatementAnalyzer,AccessControlManager: For TABLE_DATA_REWRITE also register INSERT and DELETE checks

    Coordinator->>CallTask: execute Call
    CallTask->>AccessControlManager: checkCanCallProcedure(txnId, identity, ctx, procedureName)
    AccessControlManager-->>CallTask: result
    CallTask-->>Coordinator: invoke connector procedure
    Coordinator-->>Client: return result
Loading

Class diagram for new procedure access control structures

classDiagram
    class AccessControl {
        <<interface>>
        +checkCanSelectFromColumns(transactionId, identity, context, tableName, columnOrSubfieldNames)
        +checkCanCallProcedure(transactionId, identity, context, procedureName)
    }

    class SystemAccessControl {
        <<interface>>
        +checkCanSelectFromColumns(identity, context, table, columns)
        +checkCanCallProcedure(identity, context, procedure)
    }

    class ConnectorAccessControl {
        <<interface>>
        +checkCanSelectFromColumns(transactionHandle, identity, context, tableName, columnOrSubfieldNames)
        +checkCanCallProcedure(transactionHandle, identity, context, procedureName)
    }

    class AccessDeniedException {
        +denySelectColumns(tableName, columnNames)
        +denyCallProcedure(procedureName)
        +denyCallProcedure(procedureName, extraInfo)
    }

    class AccessControlManager {
        +checkCanCallProcedure(transactionId, identity, context, procedureName)
    }

    class StatsRecordingSystemAccessControl {
        -delegate
        -stats
        +checkCanCallProcedure(identity, context, procedure)
    }

    class StatsRecordingSystemAccessControl_Stats {
        +checkCanCallProcedure:SystemAccessControlStats
        +getCheckCanCallProcedure()
    }

    class FileBasedAccessControl {
        -schemaRules
        -tableRules
        -sessionPropertyRules
        -procedureRules
        +checkCanCallProcedure(transactionHandle, identity, context, procedureName)
        -checkProcedurePermission(identity, procedureName, requiredPrivileges)
        -isDatabaseOwner(identity, schemaName)
    }

    class ProcedureAccessControlRule {
        -privileges:Set~ProcedurePrivilege~
        -userRegex:Optional~Pattern~
        -schemaRegex:Optional~Pattern~
        -procedureRegex:Optional~Pattern~
        +ProcedureAccessControlRule(privileges, userRegex, schemaRegex, procedureRegex)
        +match(user, procedure):Optional~Set~ProcedurePrivilege~~
    }

    class ProcedurePrivilege {
        <<enumeration>>
        EXECUTE
    }

    class AccessControlRules {
        -schemaRules:List~SchemaAccessControlRule~
        -tableRules:List~TableAccessControlRule~
        -sessionPropertyRules:List~SessionPropertyAccessControlRule~
        -procedureRules:List~ProcedureAccessControlRule~
        +getSchemaRules()
        +getTableRules()
        +getSessionPropertyRules()
        +getProcedureRules()
    }

    class FileBasedSystemAccessControl {
        +checkCanCallProcedure(identity, context, procedure)
    }

    class AllowAllSystemAccessControl {
        +checkCanCallProcedure(identity, context, procedure)
    }

    class ReadOnlySystemAccessControl {
        +checkCanCallProcedure(identity, context, procedure)
    }

    class DenyQueryIntegrityCheckSystemAccessControl {
        +checkCanCallProcedure(identity, context, procedure)
    }

    class AllowAllAccessControl_plugin {
        <<connector access control>>
        +checkCanCallProcedure(transactionHandle, identity, context, procedureName)
    }

    class ReadOnlyAccessControl_plugin {
        <<connector access control>>
        +checkCanCallProcedure(transactionHandle, identity, context, procedureName)
    }

    class DenyAllAccessControl_spi {
        +checkCanCallProcedure(transactionId, identity, context, procedureName)
    }

    class AllowAllAccessControl_spi {
        +checkCanCallProcedure(transactionId, identity, context, procedureName)
    }

    class ViewAccessControl {
        +checkCanCallProcedure(transactionId, identity, context, procedureName)
    }

    class SystemTableAwareAccessControl {
        -delegate:ConnectorAccessControl
        +checkCanCallProcedure(transactionHandle, identity, context, procedureName)
    }

    AccessControl <|.. AccessControlManager
    SystemAccessControl <|.. FileBasedSystemAccessControl
    SystemAccessControl <|.. AllowAllSystemAccessControl
    SystemAccessControl <|.. ReadOnlySystemAccessControl
    SystemAccessControl <|.. DenyQueryIntegrityCheckSystemAccessControl
    SystemAccessControl <|.. StatsRecordingSystemAccessControl

    ConnectorAccessControl <|.. FileBasedAccessControl
    ConnectorAccessControl <|.. AllowAllAccessControl_plugin
    ConnectorAccessControl <|.. ReadOnlyAccessControl_plugin
    ConnectorAccessControl <|.. SystemTableAwareAccessControl

    AccessControlRules o-- ProcedureAccessControlRule
    FileBasedAccessControl o-- ProcedureAccessControlRule
    ProcedureAccessControlRule ..> ProcedurePrivilege

    StatsRecordingSystemAccessControl *-- StatsRecordingSystemAccessControl_Stats

    AccessControlManager ..> SystemAccessControl
    AccessControlManager ..> ConnectorAccessControl
    AccessControlManager ..> AccessDeniedException
Loading

Class diagram for RewriteWriterTarget access checks

classDiagram
    class RewriteWriterTarget {
        -metadata:Metadata
        -accessControl:AccessControl
        +RewriteWriterTarget(metadata, accessControl)
        +optimize(plan, session, types, variableAllocator, idAllocator, warningCollector):PlanOptimizerResult
    }

    class RewriteWriterTarget_Rewriter {
        -session:Session
        -metadata:Metadata
        -accessControl:AccessControl
        -planChanged:boolean
        +Rewriter(session, metadata, accessControl)
        +visitCallDistributedProcedure(node, context):PlanNode
        +isPlanChanged():boolean
        -findTableHandleForCallDistributedProcedure(startNode):Optional~TableHandle~
        -hasFullDataAccessControl(tableHandle):boolean
    }

    class Metadata {
        +getTableMetadata(session, tableHandle):TableMetadata
        +getColumnHandles(session, tableHandle):Map~String,ColumnHandle~
        +getColumnMetadata(session, tableHandle, columnHandle):ColumnMetadata
    }

    class AccessControl {
        +getRowFilters(transactionId, identity, context, table):List~ViewExpression~
        +getColumnMasks(transactionId, identity, context, table, columns):Map~ColumnMetadata,ViewExpression~
    }

    class TableMetadata {
        +getConnectorId():ConnectorId
        +getTable():SchemaTableName
    }

    class Session {
        +getTransactionId():Optional~TransactionId~
        +getRequiredTransactionId():TransactionId
        +getIdentity():Identity
        +getAccessControlContext():AccessControlContext
    }

    class StatementAnalyzer {
        +visitCall(call, scope):Scope
    }

    class AccessControlInfoForTable {
        +AccessControlInfoForTable(accessControl, identity, transactionId, context, tableName)
    }

    class Analysis {
        +addAccessControlCheckForTable(operation, accessControlInfoForTable)
    }

    RewriteWriterTarget *-- RewriteWriterTarget_Rewriter
    RewriteWriterTarget_Rewriter ..> Metadata
    RewriteWriterTarget_Rewriter ..> AccessControl
    RewriteWriterTarget_Rewriter ..> Session
    RewriteWriterTarget_Rewriter ..> TableMetadata

    StatementAnalyzer ..> AccessControl
    StatementAnalyzer ..> Analysis
    Analysis o-- AccessControlInfoForTable

    AccessControlInfoForTable ..> AccessControl
    AccessControlInfoForTable ..> Session
Loading

File-Level Changes

Change Details Files
Enforce EXECUTE privilege checks for procedure calls at system and connector layers, including statistics and metrics wiring.
  • Extend AccessControl, SystemAccessControl, ConnectorAccessControl, and related wrapper/implementation classes to add checkCanCallProcedure methods with a default deny or pass-through behavior as appropriate (AllowAll, DenyAll, ReadOnly, FileBased, Legacy, Ranger, SqlStandard, SystemTableAware, Forwarding, ViewAccessControl, TestingAccessControlManager, etc.).
  • Update AccessDeniedException to support denyCallProcedure helpers and use them in new access-control methods.
  • Wrap system-level procedure access checks in StatsRecordingSystemAccessControl with timing and failure stats, and expose new metrics via nested SystemAccessControlStats accessors.
presto-spi/src/main/java/com/facebook/presto/spi/security/AccessControl.java
presto-spi/src/main/java/com/facebook/presto/spi/security/SystemAccessControl.java
presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorAccessControl.java
presto-spi/src/main/java/com/facebook/presto/spi/security/AccessDeniedException.java
presto-spi/src/main/java/com/facebook/presto/spi/security/AllowAllAccessControl.java
presto-spi/src/main/java/com/facebook/presto/spi/security/DenyAllAccessControl.java
presto-spi/src/main/java/com/facebook/presto/spi/security/ViewAccessControl.java
presto-main-base/src/main/java/com/facebook/presto/security/AccessControlManager.java
presto-main-base/src/main/java/com/facebook/presto/security/StatsRecordingSystemAccessControl.java
presto-main-base/src/main/java/com/facebook/presto/security/FileBasedSystemAccessControl.java
presto-main-base/src/main/java/com/facebook/presto/security/AllowAllSystemAccessControl.java
presto-main-base/src/main/java/com/facebook/presto/security/DenyQueryIntegrityCheckSystemAccessControl.java
presto-main-base/src/main/java/com/facebook/presto/security/ReadOnlySystemAccessControl.java
presto-main-base/src/main/java/com/facebook/presto/testing/TestingAccessControlManager.java
presto-main-base/src/test/java/com/facebook/presto/security/TestAccessControlManager.java
presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ForwardingConnectorAccessControl.java
presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ForwardingSystemAccessControl.java
presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/AllowAllAccessControl.java
presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ReadOnlyAccessControl.java
presto-hive/src/main/java/com/facebook/presto/hive/security/SystemTableAwareAccessControl.java
presto-hive/src/main/java/com/facebook/presto/hive/security/LegacyAccessControl.java
presto-hive/src/main/java/com/facebook/presto/hive/security/SqlStandardAccessControl.java
presto-hive/src/main/java/com/facebook/presto/hive/security/ranger/RangerBasedAccessControl.java
Add file-based procedure-level access control rules and tests for Hive and general connector tooling.
  • Extend AccessControlRules and FileBasedAccessControl to support procedureRules and checkCanCallProcedure, leveraging a new ProcedureAccessControlRule model with regex-based matching and EXECUTE privilege enforcement.
  • Gate schema create/drop/rename in FileBasedAccessControl on database ownership via isDatabaseOwner.
  • Add new JSON configuration (procedure.json, updated security.json) and unit tests to validate procedure permissions across users and schemas, including Hive file-based security behavior for procedure calls.
presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/AccessControlRules.java
presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/FileBasedAccessControl.java
presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ProcedureAccessControlRule.java
presto-plugin-toolkit/src/test/java/com/facebook/presto/plugin/base/security/TestFileBasedAccessControl.java
presto-hive/src/test/java/com/facebook/presto/hive/TestHiveFileBasedSecurity.java
presto-hive/src/test/resources/com/facebook/presto/hive/security.json
presto-plugin-toolkit/src/test/resources/procedure.json
Wire procedure EXECUTE checks into statement analysis and execution, and add table-level checks for TABLE_DATA_REWRITE distributed procedures.
  • In StatementAnalyzer.visitCall, after resolving the distributed procedure, call accessControl.checkCanCallProcedure for the resolved QualifiedObjectName.
  • For TABLE_DATA_REWRITE distributed procedures, register table INSERT and DELETE access-control checks for the target table via Analysis.addAccessControlCheckForTable with appropriate AccessControlInfoForTable instances.
  • In CallTask.execute, perform access-control checkCanCallProcedure before invoking connector procedures.
presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java
presto-main-base/src/main/java/com/facebook/presto/execution/CallTask.java
Prevent TABLE_DATA_REWRITE from running when row filters or column masks are active on the target table, and expose assertions helpers to test this.
  • Refactor RewriteWriterTarget into a Metadata+AccessControl-aware optimizer that inspects the target TableHandle for CallDistributedProcedureNode, rejecting plans when row filters or column masks are present by throwing AccessDeniedException with a clear message.
  • Add hasFullDataAccessControl helper to fetch row filters and column masks via AccessControl and Metadata APIs and determine whether full-table access is allowed.
  • Relax QueryAssertions to support session-specific assertFails and public executeExclusively for deterministic access-control mutation tests.
presto-main-base/src/main/java/com/facebook/presto/sql/planner/optimizations/RewriteWriterTarget.java
presto-main-base/src/main/java/com/facebook/presto/sql/planner/PlanOptimizers.java
presto-main-base/src/test/java/com/facebook/presto/sql/query/QueryAssertions.java
Add configurable Iceberg security with file-based access control, ensure procedures respect those rules, and add integration tests.
  • Introduce IcebergSecurityModule and SecurityConfig to configure iceberg.security as either allow-all or file, and register the appropriate access-control module via Guice conditional modules.
  • Update InternalIcebergConnectorFactory to install IcebergSecurityModule, construct a SystemTableAwareAccessControl, and pass it into IcebergConnector instead of the previous AllowAllAccessControl.
  • Add TestIcebergFileBasedSecurity with a custom Iceberg catalog and security.json to validate procedure EXECUTE checks, distributed TABLE_DATA_REWRITE behavior w.r.t INSERT/DELETE privileges, and failures when row filters/column masks are present.
presto-iceberg/src/main/java/com/facebook/presto/iceberg/InternalIcebergConnectorFactory.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/security/IcebergSecurityModule.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/security/SecurityConfig.java
presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergFileBasedSecurity.java
presto-iceberg/src/test/resources/com/facebook/presto/iceberg/security.json
presto-docs/src/main/sphinx/connector/hive-security.rst
presto-docs/src/main/sphinx/connector/iceberg.rst

Assessment against linked issues

Issue Objective Addressed Explanation
#26680 Introduce a procedure-level access control mechanism (EXECUTE privilege) for both normal and distributed procedures, analogous to table-level access control, and plumb it through the system and connector access control APIs.
#26680 For distributed TABLE_DATA_REWRITE procedures, enforce access control on the target tables so that executing the procedure requires the necessary INSERT and DELETE permissions (and disallow execution when full table data access is restricted by row filters or column masks).

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@hantangwangd hantangwangd force-pushed the access_control_for_procedures branch 2 times, most recently from 06b6b85 to 04be7a6 Compare December 18, 2025 07:35
@hantangwangd hantangwangd linked an issue Dec 18, 2025 that may be closed by this pull request
@hantangwangd hantangwangd force-pushed the access_control_for_procedures branch from 04be7a6 to db67a29 Compare December 20, 2025 06:17
@hantangwangd hantangwangd marked this pull request as ready for review December 20, 2025 13:28
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 7 issues, and left some high level feedback:

  • In StatsRecordingSystemAccessControl.checkCanCallProcedure, the failure and duration are recorded on checkCanAddColumn instead of the new checkCanCallProcedure stat, so the metrics for procedure calls will be misreported; update both the recordFailure() and record(...) calls to use stats.checkCanCallProcedure.
  • In ProcedureAccessControlRule's constructor, the schemaRegex null-check uses the message "sourceRegex is null", which looks like a copy‑paste error and should be corrected to reference schemaRegex for clearer diagnostics.
  • In TestIcebergFileBasedSecurity.testCallDistributedProceduresWithInsertDeletePermission, the expected error messages hardcode schema.test_rewrite_table instead of using the schema variable, which makes the test brittle if the schema changes; consider constructing the fully qualified table name from schema and tableName in the assertions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `StatsRecordingSystemAccessControl.checkCanCallProcedure`, the failure and duration are recorded on `checkCanAddColumn` instead of the new `checkCanCallProcedure` stat, so the metrics for procedure calls will be misreported; update both the `recordFailure()` and `record(...)` calls to use `stats.checkCanCallProcedure`.
- In `ProcedureAccessControlRule`'s constructor, the `schemaRegex` null-check uses the message "sourceRegex is null", which looks like a copy‑paste error and should be corrected to reference `schemaRegex` for clearer diagnostics.
- In `TestIcebergFileBasedSecurity.testCallDistributedProceduresWithInsertDeletePermission`, the expected error messages hardcode `schema.test_rewrite_table` instead of using the `schema` variable, which makes the test brittle if the schema changes; consider constructing the fully qualified table name from `schema` and `tableName` in the assertions.

## Individual Comments

### Comment 1
<location> `presto-main-base/src/main/java/com/facebook/presto/sql/planner/optimizations/RewriteWriterTarget.java:187-196` </location>
<code_context>
+        private boolean hasFullDataAccessControl(TableHandle tableHandle)
</code_context>

<issue_to_address>
**issue (bug_risk):** Use a required transaction ID instead of `session.getTransactionId().get()` to avoid Optional misuse

In `hasFullDataAccessControl`, `getRowFilters` and `getColumnMasks` currently call `session.getTransactionId().get()`. If no transaction ID is present (e.g. some autocommit/non-transactional flows), this will throw `NoSuchElementException` instead of an access-control error. Please use `session.getRequiredTransactionId()` here (optionally passing it into the rewriter) to match the rest of the access-control code and avoid this failure mode.
</issue_to_address>

### Comment 2
<location> `presto-main-base/src/main/java/com/facebook/presto/security/StatsRecordingSystemAccessControl.java:498-507` </location>
<code_context>
     }

+    @Override
+    public void checkCanCallProcedure(Identity identity, AccessControlContext context, CatalogSchemaTableName procedure)
+    {
+    }
</code_context>

<issue_to_address>
**issue (bug_risk):** checkCanCallProcedure is recording stats against the wrong counter (checkCanAddColumn)

In `checkCanCallProcedure`, the catch and finally blocks still use `stats.checkCanAddColumn` instead of the new `stats.checkCanCallProcedure`. This will misclassify procedure calls as "add column" operations and leave the procedure-specific metric unused. Please update those calls to `stats.checkCanCallProcedure.recordFailure()` and `stats.checkCanCallProcedure.record(duration)` to keep metrics accurate.
</issue_to_address>

### Comment 3
<location> `presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ProcedureAccessControlRule.java:43` </location>
<code_context>
+    public ProcedureAccessControlRule(
+            @JsonProperty("privileges") Set<ProcedurePrivilege> privileges,
+            @JsonProperty("user") Optional<Pattern> userRegex,
+            @JsonProperty("schema") Optional<Pattern> schemaRegex,
+            @JsonProperty("procedure") Optional<Pattern> procedureRegex)
+    {
+        this.privileges = ImmutableSet.copyOf(requireNonNull(privileges, "privileges is null"));
+        this.userRegex = requireNonNull(userRegex, "userRegex is null");
+        this.schemaRegex = requireNonNull(schemaRegex, "sourceRegex is null");
+        this.procedureRegex = requireNonNull(procedureRegex, "procedureRegex is null");
+    }
</code_context>

<issue_to_address>
**nitpick (typo):** Constructor error message refers to `sourceRegex` instead of `schemaRegex`

`this.schemaRegex` is initialized with `requireNonNull(schemaRegex, "sourceRegex is null")`. For consistency and clarity, the message should reference `schemaRegex` instead.

```suggestion
        this.schemaRegex = requireNonNull(schemaRegex, "schemaRegex is null");
```
</issue_to_address>

### Comment 4
<location> `presto-hive/src/test/java/com/facebook/presto/hive/TestHiveFileBasedSecurity.java:89` </location>
<code_context>
+        Session joe = Session.builder(getSession("joe"))
+                .setConnectionProperty(new ConnectorId("hive"), USE_LIST_DIRECTORY_CACHE, "true")
+                .build();
+        assertDenied(() -> queryRunner.execute(joe, "call hive.system.invalidate_directory_list_cache()"));
+    }
+
</code_context>

<issue_to_address>
**suggestion (testing):** Strengthen the negative assertion to check for access-denied semantics rather than any RuntimeException.

`assertDenied` currently passes on any `RuntimeException`, so this test would also succeed for unrelated failures (e.g., syntax errors or catalog misconfiguration). To better validate access control, tighten the expectation by asserting the specific access-denied exception type and/or checking that the exception message clearly indicates an access-denied condition for this procedure. That way the test only passes when the failure is truly due to access control.

Suggested implementation:

```java
        Session bob = Session.builder(getSession("bob"))
                .setConnectionProperty(new ConnectorId("hive"), USE_LIST_DIRECTORY_CACHE, "true")
                .build();
        queryRunner.execute(bob, "call hive.system.invalidate_directory_list_cache()");

        Session joe = Session.builder(getSession("joe"))
                .setConnectionProperty(new ConnectorId("hive"), USE_LIST_DIRECTORY_CACHE, "true")
                .build();

        try {
            queryRunner.execute(joe, "call hive.system.invalidate_directory_list_cache()");
            fail("Expected access to be denied for joe when calling invalidate_directory_list_cache");
        }
        catch (AccessDeniedException e) {
            assertTrue(
                    e.getMessage() != null && e.getMessage().contains("invalidate_directory_list_cache"),
                    "AccessDeniedException message should indicate invalidate_directory_list_cache was denied, but was: " + e.getMessage());
        }

```

```java
import com.facebook.presto.Session;
import com.facebook.presto.spi.ConnectorId;
import com.facebook.presto.spi.security.AccessDeniedException;
import com.facebook.presto.spi.security.Identity;
import com.facebook.presto.testing.QueryRunner;
import com.google.common.collect.ImmutableList;
import java.util.Optional;

```

```java
import static com.facebook.presto.hive.HiveQueryRunner.createQueryRunner;
import static com.facebook.presto.hive.HiveSessionProperties.USE_LIST_DIRECTORY_CACHE;
import static com.facebook.presto.testing.TestingSession.testSessionBuilder;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

```
</issue_to_address>

### Comment 5
<location> `presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergFileBasedSecurity.java:124-125` </location>
<code_context>
+            assertUpdate(icebergAdmin, format("call system.rewrite_data_files('%s', '%s')", schema, tableName), 2);
+            // `alice` and `bob` have the permission to execute `iceberg.system.rewrite_data_files`,
+            // but they lack the necessary permission to perform INSERT or DELETE on the target table
+            assertDenied(() -> assertUpdate(alice, format("call system.rewrite_data_files('%s', '%s')", schema, tableName)),
+                    "Access Denied: Cannot delete from table schema.test_rewrite_table");
+            assertDenied(() -> assertUpdate(bob, format("call system.rewrite_data_files('%s', '%s')", schema, tableName)),
+                    "Access Denied: Cannot insert into table schema.test_rewrite_table");
</code_context>

<issue_to_address>
**issue (testing):** Make the expected error messages resilient to catalog/schema naming and align them with the actual object name formatting.

These assertions hard-code `schema.test_rewrite_table`, while the actual error messages come from `QualifiedObjectName` (often including the catalog, e.g., `iceberg.<schema>.test_rewrite_table`) and use `getSession().getSchema()` instead of the literal `schema`.

To avoid brittle tests, either build the expected message with `format` using the actual schema (and catalog, if present), or match only the stable portion via regex, e.g.:
- `"Access Denied: Cannot delete from table .*\\." + tableName`
- `"Access Denied: Cannot insert into table .*\\." + tableName`

This will keep the tests stable if catalog/schema naming or formatting changes.
</issue_to_address>

### Comment 6
<location> `presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergFileBasedSecurity.java:168-174` </location>
<code_context>
+            // perform INSERT/DELETE operations on the target table involved in the procedure
+            assertUpdate(icebergAdmin, format("call system.rewrite_data_files('%s', '%s')", schema, tableName), 2);
+
+            assertions.executeExclusively(() -> {
+                accessControl.reset();
+                accessControl.rowFilter(
+                        new QualifiedObjectName("iceberg", schema, tableName),
+                        "iceberg",
+                        new ViewExpression("iceberg", Optional.empty(), Optional.empty(), "a < 2"));
+                assertions.assertQuery(icebergAdmin, "SELECT count(*) FROM " + tableName, "VALUES BIGINT '1'");
+                assertions.assertFails(icebergAdmin, format("call system.rewrite_data_files('%s', '%s')", schema, tableName),
+                        "Access Denied: Full data access is restricted by row filters and column masks");
+            });
</code_context>

<issue_to_address>
**suggestion (testing):** Consider also asserting normal read/masked behavior in the column-mask scenario to fully validate the access-control interaction.

In the row-filter case you both confirm the filter is enforced (via a `SELECT` count) and that `rewrite_data_files` is denied. In the column-mask case you only check the denial. Please also assert that a normal query still works and applies the mask (for example, selecting column `b` and verifying it reads as `'noop'`), so the test fails if masks are accidentally disabled instead of the rewrite being blocked.

```suggestion
            assertions.executeExclusively(() -> {
                accessControl.reset();
                accessControl.columnMask(
                        new QualifiedObjectName("iceberg", schema, tableName),
                        "b",
                        "iceberg",
                        new ViewExpression("iceberg", Optional.empty(), Optional.empty(), "'noop'"));
                // verify that normal reads still work and the column mask is applied
                assertions.assertQuery(
                        icebergAdmin,
                        "SELECT b FROM " + tableName + " ORDER BY a",
                        "VALUES 'noop', 'noop'");
                assertions.assertFails(
                        icebergAdmin,
                        format("call system.rewrite_data_files('%s', '%s')", schema, tableName),
                        "Access Denied: Full data access is restricted by row filters and column masks");
            });
```
</issue_to_address>

### Comment 7
<location> `presto-docs/src/main/sphinx/connector/iceberg.rst:2408-2409` </location>
<code_context>
+================================================== ============================================================
+Property Value                                     Description
+================================================== ============================================================
+``allow-all`` (default value)                      None authorization checks are enforced, thus allowing all
+                                                   operations.
+
+``file``                                           Authorization checks are enforced using a config file specified
</code_context>

<issue_to_address>
**issue (typo):** Fix grammar in the `allow-all` description (`None authorization``No authorization`).

Also consider rewriting the full sentence to: `No authorization checks are enforced, thus allowing all operations.`

```suggestion
``allow-all`` (default value)                      No authorization checks are enforced, thus allowing all
                                                   operations.
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@hantangwangd
Copy link
Member Author

@sourcery-ai resolve

@tdcmeehan tdcmeehan self-assigned this Dec 21, 2025
Copy link
Contributor

@tdcmeehan tdcmeehan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great. Can you please also add a release note?

}

@Override
public void checkCanCallProcedure(ConnectorTransactionHandle transactionHandle, ConnectorIdentity identity, AccessControlContext context, SchemaTableName procedureName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand this is done because we don't want to suddenly break existing users of Ranger. However, it doesn't seem right that simply migrating from File to Ranger would mean allowing what was once disallowed. As a compromise, I wonder if we should add a temporary config property, defaulted true, that throws if this method is called? We can mention this as a breaking change in the release note and it will allow users to turn it off if needed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this great suggestion. I agree, adding a temporary config property is a good way to unify the default access control for calling procedures. This could help us bridge the gap until ranger and sql-standard are fully supported. Will do this change.

@hantangwangd hantangwangd force-pushed the access_control_for_procedures branch 2 times, most recently from 4dcf69a to 1396638 Compare January 8, 2026 05:57
@hantangwangd hantangwangd changed the title feat: Add access control for procedures feat!: Add access control for procedures Jan 8, 2026
@hantangwangd
Copy link
Member Author

Hi @tdcmeehan, thanks for the review. I have addressed the comments by making the following updates:

  • Added a temporary configuration property hive.restrict-procedure-call for both Ranger and SQL-standard access control (defaults to true).
  • Updated the documentation accordingly.
  • Added release notes.
  • Marked this PR as a breaking change.

Please take a look when you have a chance!

Copy link
Contributor

@steveburnett steveburnett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the documentation! I appreciate the clear code examples you provide, which will help the reader a great deal.

Please read my editing suggestion about future tense, and let me know what you think!

calls are not allowed. Set it to false to allow procedure
calls. This configuration property is temporary and will be
removed when SQL Standard Based Authorization introduces
fine-grained procedure-level access control.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fine-grained procedure-level access control.

Technical documentation should describe the current state of the software. Future statements are a promise that may never happen. I do note the conditional you add, but that conditional affects code as well as documentation. I would prefer to this future concern to tracked by creating a Presto issue that says

"when SQL Standard Based Authorization introduces fine-grained procedure-level access control, deprecate the property hive.restrict-procedure-call and update the documentation by deleting the property entry. See 26803 for more information."

Let me know what you think, and how you feel about my proposed solution.

@steveburnett
Copy link
Contributor

Thanks for the release note! Some suggestions:

== RELEASE NOTES ==

Security Changes
 * Add support for procedure calls in access control.
 * Add fine-grained access control for procedure calls in the file-based access control system.
 * Add a temporary configuration property ``hive.restrict-procedure-call`` for ranger and sql-standard access control. It defaults to ``true``, meaning procedure calls are restricted.
   To allow procedure calls, set this configuration property to ``false``.

Iceberg Connector Changes
 * Add support for configuring access control in Iceberg using the ``iceberg.security`` property in the Iceberg catalog properties file. The supported types are ``allow-all`` and ``file``.

@hantangwangd
Copy link
Member Author

Hi @steveburnett, thanks a lot for your review and suggestion. I've updated the documentation and release notes based on your advice, and created two issues #26929, #26930 to track the follow-up changes. Please take a look when you get a chance.

tdcmeehan
tdcmeehan previously approved these changes Jan 9, 2026
Copy link
Contributor

@steveburnett steveburnett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the update and the new issues! One single nit of formatting.

Copy link
Contributor

@steveburnett steveburnett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! (docs)

Pull updated branch, new local doc build, looks good. Thanks!

@hantangwangd hantangwangd merged commit 3037dc9 into prestodb:master Jan 10, 2026
111 of 113 checks passed
@hantangwangd hantangwangd deleted the access_control_for_procedures branch January 10, 2026 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add access control for Procedure architecture

3 participants