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

CalvinKirs pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new bd973648f43 [fix](fe) Use service-specific Ranger select access types 
(#67206)
bd973648f43 is described below

commit bd973648f4309dc44bbcce5f175f3ae394a08c31
Author: Calvin Kirs <[email protected]>
AuthorDate: Wed Sep 2 09:43:19 2026 +0800

    [fix](fe) Use service-specific Ranger select access types (#67206)
    
    ### What problem does this PR solve?
    
    
    
    Problem Summary:
    
    `RangerAccessController` hardcoded the Doris uppercase `SELECT` access
    type for row-filter and data-mask requests. Ranger-Hive defines this
    access type as lowercase `select`. Ordinary authorization could
    initially succeed, but after Ranger initialized its optimized
    exact-match evaluator, row-filter and data-mask lookups using uppercase
    `SELECT` skipped the lowercase policies.
    
    This was reproduced against the unmodified
    `apache/doris:all-in-one-4.1.3` image. The query was authorized but
    returned unfiltered and unmasked rows:
    
    ```text
    expected: [[2, NULL]]
    actual:   [[1, first], [2, second]]
    ```
    
    This PR:
    
    - delegates SELECT mapping to each Ranger service while retaining a
    concrete uppercase default, preserving compatibility with existing
    external subclasses;
    - uses lowercase `select` consistently for Ranger-Hive authorization,
    row filters, and data masks;
    - keeps Ranger connection and process settings in the existing
    `ranger-<serviceName>-security.xml` configuration;
    - adds unit coverage and an end-to-end `ranger_p2` case covering
    lowercase access entries, row filtering, data masking, and a warmed-up
    policy evaluator.
    
    ### Validation
    
    - `./run-fe-ut.sh --run
    
org.apache.doris.catalog.authorizer.ranger.hive.RangerHiveAccessControllerTest`:
    2 tests passed, 0 failures/errors/skips.
    - `./run-regression-test.sh --run --conf <local-ranger-conf> -d
    ranger_p2 -s test_ranger_hive_lowercase_access_type`: 1 suite passed, 0
    failed, 0 skipped. The FE loaded `ranger-doris_hive-security.xml`, the
    catalog did not provide a Ranger URL, and both first and warmed-up
    queries returned the filtered and masked result.
---
 .../authorizer/ranger/RangerAccessController.java  |  11 +-
 .../ranger/hive/RangerHiveAccessController.java    |  18 +-
 .../hive/RangerHiveAccessControllerTest.java       |  62 +++++++
 .../test_ranger_hive_lowercase_access_type.out     |   7 +
 .../test_ranger_hive_lowercase_access_type.groovy  | 188 +++++++++++++++++++++
 5 files changed, 277 insertions(+), 9 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/RangerAccessController.java
 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/RangerAccessController.java
index 7a2779b43b1..eda20d58d37 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/RangerAccessController.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/RangerAccessController.java
@@ -95,9 +95,8 @@ public abstract class RangerAccessController implements 
CatalogAccessController
         RangerAccessRequestImpl request = createRequest(currentUser);
         // If the access type is not set here, it defaults to ANY1 ACCESS.
         // The internal logic of the ranger is to traverse all permission 
items.
-        // Since the ranger UI will set the access type to 'SELECT',
-        // we will keep it consistent with the UI here to avoid performance 
issues
-        request.setAccessType(DorisAccessType.SELECT.name());
+        // Keep the access type consistent with the service definition to 
avoid traversing all policy items.
+        request.setAccessType(getSelectAccessType());
         request.setResource(resource);
 
         if (LOG.isDebugEnabled()) {
@@ -125,7 +124,7 @@ public abstract class RangerAccessController implements 
CatalogAccessController
             String col) {
         RangerAccessResourceImpl resource = createResource(ctl, db, tbl, col);
         RangerAccessRequestImpl request = createRequest(currentUser);
-        request.setAccessType(DorisAccessType.SELECT.name());
+        request.setAccessType(getSelectAccessType());
         request.setResource(resource);
 
         if (LOG.isDebugEnabled()) {
@@ -167,6 +166,10 @@ public abstract class RangerAccessController implements 
CatalogAccessController
 
     protected abstract RangerAccessRequestImpl createRequest(UserIdentity 
currentUser);
 
+    protected String getSelectAccessType() {
+        return DorisAccessType.SELECT.name();
+    }
+
     protected abstract RangerAccessResourceImpl createResource(String ctl, 
String db, String tbl);
 
     protected abstract RangerAccessResourceImpl createResource(String ctl, 
String db, String tbl, String col);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessController.java
 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessController.java
index 6f862ceade1..0297f88e6e7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessController.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessController.java
@@ -43,6 +43,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -113,14 +114,16 @@ public class RangerHiveAccessController extends 
RangerAccessController {
 
     private RangerAccessRequestImpl createRequest(UserIdentity currentUser, 
HiveAccessType accessType) {
         RangerAccessRequestImpl request = createRequest(currentUser);
-        if (accessType == HiveAccessType.USE) {
-            request.setAccessType(RangerPolicyEngine.ANY_ACCESS);
-        } else {
-            request.setAccessType(accessType.name().toLowerCase());
-        }
+        request.setAccessType(toRangerAccessType(accessType));
         return request;
     }
 
+    static String toRangerAccessType(HiveAccessType accessType) {
+        return accessType == HiveAccessType.USE
+                ? RangerPolicyEngine.ANY_ACCESS
+                : accessType.name().toLowerCase(Locale.ROOT);
+    }
+
     @Override
     protected RangerAccessRequestImpl createRequest(UserIdentity currentUser) {
         RangerAccessRequestImpl request = new RangerAccessRequestImpl();
@@ -137,6 +140,11 @@ public class RangerHiveAccessController extends 
RangerAccessController {
         return request;
     }
 
+    @Override
+    protected String getSelectAccessType() {
+        return toRangerAccessType(HiveAccessType.SELECT);
+    }
+
     private void checkPrivileges(UserIdentity currentUser, HiveAccessType 
accessType,
             List<RangerHiveResource> hiveResources) throws 
AuthorizationException {
         lifecycleLock.readLock().lock();
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessControllerTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessControllerTest.java
new file mode 100644
index 00000000000..95c3cc2a690
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessControllerTest.java
@@ -0,0 +1,62 @@
+// 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.
+
+package org.apache.doris.catalog.authorizer.ranger.hive;
+
+import org.apache.doris.analysis.UserIdentity;
+
+import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl;
+import org.apache.ranger.plugin.policyengine.RangerPolicyEngine;
+import org.apache.ranger.plugin.service.RangerBasePlugin;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.lang.reflect.Field;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public class RangerHiveAccessControllerTest {
+    @Test
+    public void testRangerAccessTypeMapping() {
+        Assert.assertEquals("select", 
RangerHiveAccessController.toRangerAccessType(HiveAccessType.SELECT));
+        Assert.assertEquals("update", 
RangerHiveAccessController.toRangerAccessType(HiveAccessType.UPDATE));
+        Assert.assertEquals(RangerPolicyEngine.ANY_ACCESS,
+                
RangerHiveAccessController.toRangerAccessType(HiveAccessType.USE));
+    }
+
+    @Test
+    public void testPolicyRequestsUseLowerCaseSelect() throws Exception {
+        RangerHiveAccessController controller = Mockito.mock(
+                RangerHiveAccessController.class, Mockito.CALLS_REAL_METHODS);
+        Field lifecycleLock = 
RangerHiveAccessController.class.getDeclaredField("lifecycleLock");
+        lifecycleLock.setAccessible(true);
+        lifecycleLock.set(controller, new ReentrantReadWriteLock());
+        RangerBasePlugin plugin = Mockito.mock(RangerBasePlugin.class);
+        UserIdentity currentUser = 
UserIdentity.createAnalyzedUserIdentWithIp("user", "%");
+        RangerAccessRequestImpl rowFilterRequest = new 
RangerAccessRequestImpl();
+        RangerAccessRequestImpl dataMaskRequest = new 
RangerAccessRequestImpl();
+
+        Mockito.doReturn(rowFilterRequest, 
dataMaskRequest).when(controller).createRequest(currentUser);
+        Mockito.doReturn(plugin).when(controller).getPlugin();
+
+        controller.evalRowFilterPolicies(currentUser, "catalog", "database", 
"table");
+        controller.evalDataMaskPolicy(currentUser, "catalog", "database", 
"table", "column");
+
+        Assert.assertEquals("select", rowFilterRequest.getAccessType());
+        Assert.assertEquals("select", dataMaskRequest.getAccessType());
+    }
+}
diff --git 
a/regression-test/data/ranger_p2/test_ranger_hive_lowercase_access_type.out 
b/regression-test/data/ranger_p2/test_ranger_hive_lowercase_access_type.out
new file mode 100644
index 00000000000..0a48252701f
--- /dev/null
+++ b/regression-test/data/ranger_p2/test_ranger_hive_lowercase_access_type.out
@@ -0,0 +1,7 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !lowercase_access_type_first --
+2      \N
+
+-- !lowercase_access_type_warmed_up --
+2      \N
+
diff --git 
a/regression-test/suites/ranger_p2/test_ranger_hive_lowercase_access_type.groovy
 
b/regression-test/suites/ranger_p2/test_ranger_hive_lowercase_access_type.groovy
new file mode 100644
index 00000000000..876bd0b7019
--- /dev/null
+++ 
b/regression-test/suites/ranger_p2/test_ranger_hive_lowercase_access_type.groovy
@@ -0,0 +1,188 @@
+// 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.
+
+import org.apache.ranger.RangerClient
+import org.apache.ranger.plugin.model.RangerPolicy
+import org.apache.ranger.plugin.model.RangerService
+
+
+suite("test_ranger_hive_lowercase_access_type", "p2,ranger,external") {
+    String enableRangerTest = 
context.config.otherConfigs.get("enableRangerTest")
+    String enableHiveTest = context.config.otherConfigs.get("enableHiveTest")
+
+    if (!enableRangerTest?.equalsIgnoreCase("true") || 
!enableHiveTest?.equalsIgnoreCase("true")) {
+        logger.info("skip Ranger-Hive lowercase access type case because 
Ranger or Hive test is not enabled")
+        return
+    }
+
+    String rangerEndpoint = context.config.otherConfigs.get("rangerEndpoint")
+    String rangerUser = context.config.otherConfigs.get("rangerUser")
+    String rangerPassword = context.config.otherConfigs.get("rangerPassword")
+    String rangerServiceName = 
context.config.otherConfigs.get("rangerServiceName")
+    String externalEnvIp = context.config.otherConfigs.get("externalEnvIp")
+    String hmsPort = context.config.otherConfigs.get("hive3HmsPort")
+    String hiveServerPort = context.config.otherConfigs.get("hive3ServerPort")
+    String rangerHiveServiceName = "${rangerServiceName}_hive"
+
+    String catalog = "ranger_hive_lowercase_catalog"
+    String database = "ranger_hive_lowercase_db"
+    String table = "ranger_hive_lowercase_tbl"
+    String user = "ranger_hive_lowercase_user"
+    String password = "C123_567p"
+    String accessPolicyName = "doris_ranger_hive_lowercase_access"
+    String rowFilterPolicyName = "doris_ranger_hive_lowercase_row_filter"
+    String dataMaskPolicyName = "doris_ranger_hive_lowercase_data_mask"
+    List<String> policyNames = [accessPolicyName, rowFilterPolicyName, 
dataMaskPolicyName]
+
+    RangerClient rangerClient = new RangerClient(
+            "http://${rangerEndpoint}";, "simple", rangerUser, rangerPassword, 
null)
+    boolean createdHiveService = false
+
+    def deletePolicyIfExists = { String policyName ->
+        try {
+            rangerClient.deletePolicy(rangerHiveServiceName, policyName)
+        } catch (Exception e) {
+            logger.info("Ranger policy ${policyName} does not exist: 
${e.getMessage()}")
+        }
+    }
+
+    def tableResources = {
+        Map<String, RangerPolicy.RangerPolicyResource> resources = new 
HashMap<>()
+        resources.put("database", new 
RangerPolicy.RangerPolicyResource(database))
+        resources.put("table", new RangerPolicy.RangerPolicyResource(table))
+        return resources
+    }
+
+    def accessResources = {
+        Map<String, RangerPolicy.RangerPolicyResource> resources = 
tableResources()
+        resources.put("column", new RangerPolicy.RangerPolicyResource("*"))
+        return resources
+    }
+
+    try {
+        try {
+            RangerService hiveService = 
rangerClient.getService(rangerHiveServiceName)
+            assertEquals("hive", hiveService.getType())
+        } catch (Exception e) {
+            logger.info("Create Ranger-Hive service ${rangerHiveServiceName}: 
${e.getMessage()}")
+            RangerService hiveService = new RangerService()
+            hiveService.setType("hive")
+            hiveService.setName(rangerHiveServiceName)
+            hiveService.setDisplayName(rangerHiveServiceName)
+            hiveService.setConfigs([
+                    "username": "hive",
+                    "password": "hive",
+                    "jdbc.driverClassName": "org.apache.hive.jdbc.HiveDriver",
+                    "jdbc.url": 
"jdbc:hive2://${externalEnvIp}:${hiveServerPort}".toString()
+            ])
+            rangerClient.createService(hiveService)
+            createdHiveService = true
+        }
+
+        // Prepare the Hive data through a catalog without Ranger, so setup 
does not need policies.
+        sql """DROP CATALOG IF EXISTS `${catalog}`"""
+        sql """CREATE CATALOG `${catalog}` PROPERTIES (
+                'type' = 'hms',
+                'hive.metastore.uris' = 'thrift://${externalEnvIp}:${hmsPort}'
+        )"""
+        try_sql """DROP TABLE IF EXISTS 
`${catalog}`.`${database}`.`${table}`"""
+        try_sql """DROP DATABASE IF EXISTS `${catalog}`.`${database}`"""
+        sql """CREATE DATABASE `${catalog}`.`${database}`"""
+        sql """CREATE TABLE `${catalog}`.`${database}`.`${table}` (
+                id BIGINT,
+                secret VARCHAR(20)
+        ) ENGINE=hive
+        PROPERTIES ('file_format' = 'parquet')"""
+        sql """INSERT INTO `${catalog}`.`${database}`.`${table}` VALUES
+                (1, 'first'), (2, 'second')"""
+        sql """DROP CATALOG `${catalog}`"""
+
+        sql """DROP USER IF EXISTS '${user}'"""
+        sql """CREATE USER '${user}' IDENTIFIED BY '${password}'"""
+
+        policyNames.each { deletePolicyIfExists(it) }
+
+        RangerPolicy accessPolicy = new RangerPolicy()
+        accessPolicy.setService(rangerHiveServiceName)
+        accessPolicy.setName(accessPolicyName)
+        accessPolicy.setResources(accessResources())
+        RangerPolicy.RangerPolicyItem accessPolicyItem = new 
RangerPolicy.RangerPolicyItem()
+        accessPolicyItem.setUsers([user])
+        accessPolicyItem.setAccesses([new 
RangerPolicy.RangerPolicyItemAccess("select")])
+        accessPolicy.setPolicyItems([accessPolicyItem])
+        rangerClient.createPolicy(accessPolicy)
+
+        RangerPolicy rowFilterPolicy = new RangerPolicy()
+        rowFilterPolicy.setService(rangerHiveServiceName)
+        rowFilterPolicy.setName(rowFilterPolicyName)
+        rowFilterPolicy.setPolicyType(RangerPolicy.POLICY_TYPE_ROWFILTER)
+        rowFilterPolicy.setResources(tableResources())
+        RangerPolicy.RangerRowFilterPolicyItem rowFilterPolicyItem = new 
RangerPolicy.RangerRowFilterPolicyItem()
+        rowFilterPolicyItem.setUsers([user])
+        rowFilterPolicyItem.setAccesses([new 
RangerPolicy.RangerPolicyItemAccess("select")])
+        rowFilterPolicyItem.setRowFilterInfo(new 
RangerPolicy.RangerPolicyItemRowFilterInfo("id >= 2"))
+        rowFilterPolicy.setRowFilterPolicyItems([rowFilterPolicyItem])
+        rangerClient.createPolicy(rowFilterPolicy)
+
+        RangerPolicy dataMaskPolicy = new RangerPolicy()
+        dataMaskPolicy.setService(rangerHiveServiceName)
+        dataMaskPolicy.setName(dataMaskPolicyName)
+        dataMaskPolicy.setPolicyType(RangerPolicy.POLICY_TYPE_DATAMASK)
+        Map<String, RangerPolicy.RangerPolicyResource> dataMaskResources = 
tableResources()
+        dataMaskResources.put("column", new 
RangerPolicy.RangerPolicyResource("secret"))
+        dataMaskPolicy.setResources(dataMaskResources)
+        RangerPolicy.RangerDataMaskPolicyItem dataMaskPolicyItem = new 
RangerPolicy.RangerDataMaskPolicyItem()
+        dataMaskPolicyItem.setUsers([user])
+        dataMaskPolicyItem.setAccesses([new 
RangerPolicy.RangerPolicyItemAccess("select")])
+        dataMaskPolicyItem.setDataMaskInfo(
+                new RangerPolicy.RangerPolicyItemDataMaskInfo("MASK_NULL", "", 
""))
+        dataMaskPolicy.setDataMaskPolicyItems([dataMaskPolicyItem])
+        rangerClient.createPolicy(dataMaskPolicy)
+
+        // Recreate the catalog with Ranger-Hive enabled after policies are 
ready.
+        sql """CREATE CATALOG `${catalog}` PROPERTIES (
+                'type' = 'hms',
+                'hive.metastore.uris' = 'thrift://${externalEnvIp}:${hmsPort}',
+                'access_controller.properties.ranger.service.name' = 
'${rangerHiveServiceName}',
+                'access_controller.class' =
+                        
'org.apache.doris.catalog.authorizer.ranger.hive.RangerHiveAccessControllerFactory'
+        )"""
+        waitPolicyEffect()
+
+        def tokens = context.config.jdbcUrl.split('/')
+        def defaultJdbcUrl = tokens[0] + "//" + tokens[2] + "/?"
+        connect(user, password, defaultJdbcUrl) {
+            String query = "SELECT id, secret FROM 
`${catalog}`.`${database}`.`${table}` ORDER BY id"
+
+            // The first query initializes Ranger's optimized policy 
evaluator. The second query verifies
+            // that lowercase Hive access types still match after that 
evaluator has been warmed up.
+            order_qt_lowercase_access_type_first(query)
+            order_qt_lowercase_access_type_warmed_up(query)
+        }
+    } finally {
+        try_sql "DROP CATALOG IF EXISTS `${catalog}`"
+        policyNames.each { deletePolicyIfExists(it) }
+        try_sql "DROP USER IF EXISTS '${user}'"
+        if (createdHiveService) {
+            try {
+                rangerClient.deleteService(rangerHiveServiceName)
+            } catch (Exception e) {
+                logger.warn("Failed to delete Ranger-Hive service 
${rangerHiveServiceName}: ${e.getMessage()}")
+            }
+        }
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to