This is an automated email from the ASF dual-hosted git repository.
laszlog pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git
The following commit(s) were added to refs/heads/master by this push:
new a398f7992 IMPALA-10913: Produce Ranger audit log for SHOW DATABASES
a398f7992 is described below
commit a398f7992e8a94e20861ffe81d3a84f186c406da
Author: Fang-Yu Rao <[email protected]>
AuthorDate: Fri Jan 16 15:54:31 2026 -0800
IMPALA-10913: Produce Ranger audit log for SHOW DATABASES
This patch makes Impala produce Ranger audit log for the SHOW DATABASES
and the SHOW DATABASES LIKE statements. Moreover, this patch enforces
the authorization check for the default database, meaning that the
default database will not be shown if the requesting user is not
authorized to view this database according to the Ranger policy
repository. To support this new type of authorization check, we added
the class AuthorizableDbList to represent the object to be accessed by
those two statements.
Note that this patch generates the same RangerAccessRequestImpl for the
SHOW DATABASES statement as Hive does when Ranger is the authorization
provider. Specifically, in
https://github.com/apache/ranger/blob/668b80b/hive-agent/src/main/java/org/apache/ranger/authorization/hive/authorizer/RangerHiveAuthorizer.java#L806-L811
for the operation of SHOWDATABASES, the constructor of
RangerHiveResource does not populate any field in
RangerAccessResourceImpl with a non-null value. Moreover, when
HiveAccessType.USE is passed to the constructor of
RangerHiveAccessRequest, under the covers 'accessType' in
RangerAccessRequestImpl will be set to "_any" as shown in
RangerHiveAccessRequest#setHiveAccessType().
Testing:
- Added test cases to make sure the Ranger audit event will be
produced.
- Added test cases to verify the database 'default' will not be shown
if the requesting user is not allowed to discover the database based
on the Ranger policy repository.
Change-Id: Idb3e54b152e953916d3d7d7ef27c880a8559ed26
Reviewed-on: http://gerrit.cloudera.org:8080/23877
Reviewed-by: Quanlong Huang <[email protected]>
Tested-by: Impala Public Jenkins <[email protected]>
---
.../org/apache/impala/analysis/ShowDbsStmt.java | 4 +-
.../apache/impala/authorization/Authorizable.java | 3 +-
.../impala/authorization/AuthorizableDbList.java | 31 +++++++++++++
.../impala/authorization/AuthorizableFactory.java | 2 +
.../authorization/DefaultAuthorizableFactory.java | 5 ++
.../authorization/PrivilegeRequestBuilder.java | 6 +++
.../ranger/RangerAuthorizationChecker.java | 9 +++-
.../java/org/apache/impala/service/Frontend.java | 4 --
.../authorization/ranger/RangerAuditLogTest.java | 12 +++++
tests/authorization/test_ranger.py | 54 ++++++++++++++++++++++
10 files changed, 123 insertions(+), 7 deletions(-)
diff --git a/fe/src/main/java/org/apache/impala/analysis/ShowDbsStmt.java
b/fe/src/main/java/org/apache/impala/analysis/ShowDbsStmt.java
index fef4ac2e1..60d9742ea 100644
--- a/fe/src/main/java/org/apache/impala/analysis/ShowDbsStmt.java
+++ b/fe/src/main/java/org/apache/impala/analysis/ShowDbsStmt.java
@@ -17,6 +17,7 @@
package org.apache.impala.analysis;
+import org.apache.impala.authorization.Privilege;
import org.apache.impala.common.AnalysisException;
import org.apache.impala.thrift.TShowDbsParams;
@@ -63,7 +64,8 @@ public class ShowDbsStmt extends StatementBase {
@Override
public void analyze(Analyzer analyzer) throws AnalysisException {
- // Nothing to do here
+ analyzer.registerPrivReq(builder ->
+ builder.allOf(Privilege.ANY).onDbList().build());
}
public TShowDbsParams toThrift() {
diff --git a/fe/src/main/java/org/apache/impala/authorization/Authorizable.java
b/fe/src/main/java/org/apache/impala/authorization/Authorizable.java
index fe0f9d168..e1f12261c 100644
--- a/fe/src/main/java/org/apache/impala/authorization/Authorizable.java
+++ b/fe/src/main/java/org/apache/impala/authorization/Authorizable.java
@@ -32,7 +32,8 @@ public abstract class Authorizable {
COLUMN,
FUNCTION,
URI,
- STORAGEHANDLER_URI
+ STORAGEHANDLER_URI,
+ DB_LIST
}
// Returns the name of the object.
diff --git
a/fe/src/main/java/org/apache/impala/authorization/AuthorizableDbList.java
b/fe/src/main/java/org/apache/impala/authorization/AuthorizableDbList.java
new file mode 100644
index 000000000..21559278f
--- /dev/null
+++ b/fe/src/main/java/org/apache/impala/authorization/AuthorizableDbList.java
@@ -0,0 +1,31 @@
+// 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.impala.authorization;
+
+public class AuthorizableDbList extends Authorizable {
+
+ @Override
+ public String getName() {
+ return "DbList";
+ }
+
+ @Override
+ public Type getType() {
+ return Type.DB_LIST;
+ }
+}
diff --git
a/fe/src/main/java/org/apache/impala/authorization/AuthorizableFactory.java
b/fe/src/main/java/org/apache/impala/authorization/AuthorizableFactory.java
index 750c1a8e7..e5f1ca21c 100644
--- a/fe/src/main/java/org/apache/impala/authorization/AuthorizableFactory.java
+++ b/fe/src/main/java/org/apache/impala/authorization/AuthorizableFactory.java
@@ -74,4 +74,6 @@ public interface AuthorizableFactory {
Authorizable newFunction(String dbName, String fnName);
Authorizable newStorageHandlerUri(String storageType, String storageUri);
+
+ Authorizable newDbList();
}
diff --git
a/fe/src/main/java/org/apache/impala/authorization/DefaultAuthorizableFactory.java
b/fe/src/main/java/org/apache/impala/authorization/DefaultAuthorizableFactory.java
index 7cee989fa..6fb1754c5 100644
---
a/fe/src/main/java/org/apache/impala/authorization/DefaultAuthorizableFactory.java
+++
b/fe/src/main/java/org/apache/impala/authorization/DefaultAuthorizableFactory.java
@@ -85,4 +85,9 @@ public class DefaultAuthorizableFactory implements
AuthorizableFactory {
Preconditions.checkNotNull(storageUri);
return new AuthorizableStorageHandlerUri(storageType, storageUri);
}
+
+ @Override
+ public Authorizable newDbList() {
+ return new AuthorizableDbList();
+ }
}
diff --git
a/fe/src/main/java/org/apache/impala/authorization/PrivilegeRequestBuilder.java
b/fe/src/main/java/org/apache/impala/authorization/PrivilegeRequestBuilder.java
index 402a01178..7a235fad7 100644
---
a/fe/src/main/java/org/apache/impala/authorization/PrivilegeRequestBuilder.java
+++
b/fe/src/main/java/org/apache/impala/authorization/PrivilegeRequestBuilder.java
@@ -74,6 +74,12 @@ public class PrivilegeRequestBuilder {
return this;
}
+ public PrivilegeRequestBuilder onDbList() {
+ Preconditions.checkState(authorizable_ == null);
+ authorizable_ = authzFactory_.newDbList();
+ return this;
+ }
+
/**
* Determines whether the given FeTable corresponds to a view that was
created by a
* non-superuser in HiveMetaStore.
diff --git
a/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationChecker.java
b/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationChecker.java
index f062b6240..207303003 100644
---
a/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationChecker.java
+++
b/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationChecker.java
@@ -164,6 +164,12 @@ public class RangerAuthorizationChecker extends
BaseAuthorizationChecker {
.storageUri(authorizable.getStorageUri())
.build());
break;
+ case DB_LIST:
+ // This should happen for SHOW DATABASES only.
+ RangerAccessResourceImpl resource = new RangerAccessResourceImpl();
+ resource.setValue("database", null);
+ resources.add(resource);
+ break;
default:
throw new IllegalArgumentException(String.format("Invalid authorizable
type: %s",
authorizable.getType()));
@@ -665,7 +671,8 @@ public class RangerAuthorizationChecker extends
BaseAuthorizationChecker {
// If 'resource' is associated with a storage handler URI, then
'accessType' can only
// be RWSTORAGE since RWSTORAGE is the only valid privilege that could be
applied on
// a storage handler URI.
- if (resource.getKeys().contains(RangerImpalaResourceBuilder.STORAGE_TYPE))
{
+ if (resource.getKeys() != null &&
+ resource.getKeys().contains(RangerImpalaResourceBuilder.STORAGE_TYPE))
{
accessType = Privilege.RWSTORAGE.name().toLowerCase();
} else {
if (privilege == Privilege.ANY) {
diff --git a/fe/src/main/java/org/apache/impala/service/Frontend.java
b/fe/src/main/java/org/apache/impala/service/Frontend.java
index b08c6ff72..894a53c20 100644
--- a/fe/src/main/java/org/apache/impala/service/Frontend.java
+++ b/fe/src/main/java/org/apache/impala/service/Frontend.java
@@ -1592,10 +1592,6 @@ public class Frontend {
private boolean isAccessibleToUser(String dbName, String tblName,
String owner, User user) throws InternalException {
Preconditions.checkNotNull(dbName);
- if (tblName == null && dbName.equalsIgnoreCase(Catalog.DEFAULT_DB)) {
- // Default DB should always be shown.
- return true;
- }
PrivilegeRequestBuilder builder = new PrivilegeRequestBuilder(
authzFactory_.getAuthorizableFactory())
diff --git
a/fe/src/test/java/org/apache/impala/authorization/ranger/RangerAuditLogTest.java
b/fe/src/test/java/org/apache/impala/authorization/ranger/RangerAuditLogTest.java
index 587f37fe0..7dd779ef3 100644
---
a/fe/src/test/java/org/apache/impala/authorization/ranger/RangerAuditLogTest.java
+++
b/fe/src/test/java/org/apache/impala/authorization/ranger/RangerAuditLogTest.java
@@ -290,6 +290,18 @@ public class RangerAuditLogTest extends
AuthorizationTestBase {
assertEquals(0, events.size());
}, "select * from functional.non_existing_tbl",
/* expectAnalysisOk */ false, onDatabase("functional",
TPrivilegeLevel.SELECT));
+
+ authzOk(events -> {
+ assertEquals(1, events.size());
+ assertEventEquals(null, "_ANY", null, 1, events.get(0));
+ assertEquals("show databases", events.get(0).getRequestData());
+ }, "show databases");
+
+ authzOk(events -> {
+ assertEquals(1, events.size());
+ assertEventEquals(null, "_ANY", null, 1, events.get(0));
+ assertEquals("show databases like 'default*'",
events.get(0).getRequestData());
+ }, "show databases like 'default*'");
}
@Test
diff --git a/tests/authorization/test_ranger.py
b/tests/authorization/test_ranger.py
index 87bac2d4c..4c090cf26 100644
--- a/tests/authorization/test_ranger.py
+++ b/tests/authorization/test_ranger.py
@@ -794,6 +794,26 @@ class TestRanger(CustomClusterTestSuite):
auth=RANGER_AUTH, headers=REST_HEADERS)
assert 300 > r.status_code >= 200, bytes_to_str(r.content)
+ @staticmethod
+ def _toggle_ranger_policy(self, policy_name, enable):
+ r = requests.get(
+ "{0}/service/public/v2/api/service/test_impala/policy/"
+ "{1}".format(RANGER_HOST, policy_name),
+ auth=RANGER_AUTH, headers=REST_HEADERS)
+ # We do not return an AssertionError if the policy specified by
'policy_name' is
+ # not found since this type of error should be considered benign.
+ if r.status_code != 404:
+ assert 300 > r.status_code >= 200, bytes_to_str(r.content)
+
+ current_policy = r.json()
+ current_policy['isEnabled'] = enable
+ policy_id = current_policy['id']
+ r = requests.put(
+ "{0}/service/public/v2/api/policy/{1}"
+ .format(RANGER_HOST, policy_id), json=current_policy, auth=RANGER_AUTH,
+ headers=REST_HEADERS)
+ assert 300 > r.status_code >= 200, bytes_to_str(r.content)
+
@staticmethod
def _check_privileges(result, expected):
TestRanger._check_rows(result, expected, True)
@@ -3441,6 +3461,40 @@ class TestRangerLocalCatalog(TestRanger):
for statement in cleanup_statements:
admin_client.execute(statement)
+ @pytest.mark.execute_serially
+ def test_show_databases(self):
+ """Test that SHOW DATABASES and SHOW DATABASES LIKE do not return the
database
+ 'default' when there is no Ranger policy allowing the requesting user to
see the
+ database.
+ """
+ # 'policy_name' is the name of the policy corresponding to all the tables
and columns
+ # in the database 'default'.
+ policy_name = "default%20database%20tables%20columns"
+ with self.create_impala_client(user=ADMIN) as admin_client, \
+ self.create_impala_client(user=NON_OWNER) as non_owner_client:
+ try:
+ # By the default, Ranger policy repository allows every user to
discover the
+ # existence of the database 'default' for SHOW DATABASES and SHOW
DATABASES LIKE.
+ result_1 = non_owner_client.execute("show databases")
+ TestRanger._check_rows(result_1, [['default', 'Default Hive
database']])
+
+ result_2 = non_owner_client.execute("show databases like 'default*'")
+ TestRanger._check_rows(result_2, [['default', 'Default Hive
database']])
+
+ # Disable the policy that allows every user to determine the existence
of the
+ # database 'default'.
+ TestRanger._toggle_ranger_policy(self, policy_name, False)
+ admin_client.execute("refresh authorization")
+
+ result_3 = non_owner_client.execute("show databases")
+ TestRanger._check_rows(result_3, [])
+
+ result_4 = non_owner_client.execute("show databases like 'default*'")
+ TestRanger._check_rows(result_4, [])
+ finally:
+ TestRanger._toggle_ranger_policy(self, policy_name, True)
+ admin_client.execute("refresh authorization")
+
class TestRangerColumnMaskingTpchNested(CustomClusterTestSuite):
"""