This is an automated email from the ASF dual-hosted git repository.
csringhofer 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 09bed366f IMPALA-14507: Register column-level privilege requests for
INSERT
09bed366f is described below
commit 09bed366fe7e681108f164ee53433b791dada90a
Author: Fang-Yu Rao <[email protected]>
AuthorDate: Tue Oct 21 17:24:10 2025 -0700
IMPALA-14507: Register column-level privilege requests for INSERT
This patch registers column-level privilege requests for columns
involved in the INSERT statement so that the requesting user does not
need to be granted the INSERT privilege on the entire table. This would
be helpful in the case when different users are allowed to insert data
into different sets of columns in the same table. Moreover, column-level
Ranger audit events for INSERT requests will be produced after this
patch.
This would also allow an administrator to add deny policies on columns
against a user if we would like to prevent the user from inserting data
into the specified columns.
On the other hand, this patch slightly revises the Preconditions checks
in BaseAuthorizationChecker#authorize() so that it is easier to
understand what those checks verify. The code comment there explicitly
mentions that for a statement that may produce several hierarchical
privilege requests, it should always have a corresponding table-level
privilege request if it has a column-level privilege request. This was
not entirely true and could not be detected by the previous checks.
Specifically, for the CREATE TABLE <db>.<target_tbl> AS SELECT
statement, we would register an ANY privilege request for the column
with wildcard table and column names denoting any column and table in
the database the target table belongs, i.e., '<db>.*.*', whereas there
was no privilege request for the table '<db>.*' registered. This patch
corrects this by registering an ANY privilege request for the database
of the target table instead. Due to this, we also changed the expected
error messages for some Java and end-to-end authorization-related tests.
This should not affect the security in that the resulting
RangerResourceImpl sent to the Ranger plug-in is the same whether the
privilege request is an ANY privilege request for the wildcard column
'<db>.*.*' or an ANY privilege request for the database '<db>'. Refer to
RangerAuthorizationChecker#authorizeResource() for more details.
Testing:
- Added frontend and end-to-end tests to verify that
a) we register column-level privilege requests in the INSERT
statement in addition to the table-level one,
b) a user is not allowed to insert data into a column of a table
if there is a deny policy defined on the column against the user,
even though the user was already granted the INSERT privilege on
the table,
c) a user is not allowed to insert data into a column if there is a
column masking policy defined on any column of the same table,
even though the user was already granted the INSERT privilege on
the table (RANGER-1087 and IMPALA-10554),
d) a user is allowed to insert data into a set of columns of a table
as long as the user was granted the INSERT privileges on those
columns given that there is no deny policy on those columns and
no column masking policy on any column of the same table,
e) column-level Ranger audit events could be produced after the
introduction of column-level INSERT privilege,
f) we are able to grant, revoke column-level INSERT privileges via
the catalog server, and show column-level INSERT privileges via a
coordinator.
Change-Id: I2ef61801d3b394c56702b193c250492a62b111df
Reviewed-on: http://gerrit.cloudera.org:8080/23569
Reviewed-by: Quanlong Huang <[email protected]>
Tested-by: Impala Public Jenkins <[email protected]>
Reviewed-by: Csaba Ringhofer <[email protected]>
---
.../java/org/apache/impala/analysis/Analyzer.java | 2 +-
.../org/apache/impala/analysis/InsertStmt.java | 10 +
.../org/apache/impala/analysis/PrivilegeSpec.java | 7 +-
.../impala/authorization/AuthorizableColumn.java | 29 +-
.../impala/authorization/AuthorizableTable.java | 2 +-
.../authorization/BaseAuthorizationChecker.java | 80 ++++--
.../ranger/RangerAuthorizationChecker.java | 78 ++++--
.../ranger/RangerAuthorizationContext.java | 8 +-
.../impala/analysis/AnalyzeAuthStmtsTest.java | 9 +-
.../authorization/AuthorizationStmtTest.java | 80 +++++-
.../authorization/AuthorizationTestBase.java | 40 +++
.../authorization/ranger/RangerAuditLogTest.java | 191 ++++++++++++-
.../queries/QueryTest/grant_revoke.test | 4 +-
tests/authorization/test_ranger.py | 301 +++++++++++++++++++--
14 files changed, 720 insertions(+), 121 deletions(-)
diff --git a/fe/src/main/java/org/apache/impala/analysis/Analyzer.java
b/fe/src/main/java/org/apache/impala/analysis/Analyzer.java
index 270857332..4109ea174 100644
--- a/fe/src/main/java/org/apache/impala/analysis/Analyzer.java
+++ b/fe/src/main/java/org/apache/impala/analysis/Analyzer.java
@@ -4092,7 +4092,7 @@ public class Analyzer {
}
if (privilege == Privilege.ANY) {
String dbOwner = db == null ? null : db.getOwnerUser();
- return builder.any().onAnyColumn(dbName, dbOwner).build();
+ return builder.any().onDb(dbName, dbOwner).build();
} else if (db == null) {
// Db does not exist, register a privilege request based on the DB
name.
return builder.allOf(privilege).onDb(dbName, null).build();
diff --git a/fe/src/main/java/org/apache/impala/analysis/InsertStmt.java
b/fe/src/main/java/org/apache/impala/analysis/InsertStmt.java
index b1d5381bc..ab565fce9 100644
--- a/fe/src/main/java/org/apache/impala/analysis/InsertStmt.java
+++ b/fe/src/main/java/org/apache/impala/analysis/InsertStmt.java
@@ -371,6 +371,11 @@ public class InsertStmt extends DmlStatementBase {
"Duplicate column '" + columnName + "' in column permutation");
}
selectExprTargetColumns.add(column);
+
+ analyzer.registerPrivReq(builder -> builder
+ .allOf(Privilege.INSERT)
+ .onColumn(table_.getTableName().getDb(),
table_.getTableName().getTbl(),
+ column.getName(), table_.getOwnerUser()).build());
}
int numStaticPartitionExprs = 0;
@@ -396,6 +401,11 @@ public class InsertStmt extends DmlStatementBase {
} else {
selectExprTargetColumns.add(column);
}
+
+ analyzer.registerPrivReq(builder -> builder
+ .allOf(Privilege.INSERT)
+ .onColumn(table_.getTableName().getDb(),
table_.getTableName().getTbl(),
+ column.getName(), table_.getOwnerUser()).build());
}
}
diff --git a/fe/src/main/java/org/apache/impala/analysis/PrivilegeSpec.java
b/fe/src/main/java/org/apache/impala/analysis/PrivilegeSpec.java
index aab0948c5..aba9d2744 100644
--- a/fe/src/main/java/org/apache/impala/analysis/PrivilegeSpec.java
+++ b/fe/src/main/java/org/apache/impala/analysis/PrivilegeSpec.java
@@ -270,7 +270,7 @@ public class PrivilegeSpec extends StmtNode {
* 1. No columns are specified.
* 2. Privilege is applied on a view or an external data source.
* 3. Referenced table and/or columns do not exist.
- * 4. Privilege level is not SELECT.
+ * 4. Privilege level is neither SELECT nor INSERT.
*/
private void analyzeColumnPrivScope(Analyzer analyzer) throws
AnalysisException {
Preconditions.checkState(scope_ == TPrivilegeScope.COLUMN);
@@ -278,8 +278,9 @@ public class PrivilegeSpec extends StmtNode {
if (columnNames_.isEmpty()) {
throw new AnalysisException("Empty column list in column privilege
spec.");
}
- if (privilegeLevel_ != TPrivilegeLevel.SELECT) {
- throw new AnalysisException("Only 'SELECT' privileges are allowed " +
+ if (privilegeLevel_ != (TPrivilegeLevel.SELECT) &&
+ privilegeLevel_ != (TPrivilegeLevel.INSERT)) {
+ throw new AnalysisException("Only 'SELECT' and 'INSERT' privileges are
allowed " +
"in a column privilege spec.");
}
FeTable table = analyzeTargetTable(analyzer);
diff --git
a/fe/src/main/java/org/apache/impala/authorization/AuthorizableColumn.java
b/fe/src/main/java/org/apache/impala/authorization/AuthorizableColumn.java
index e0a2bdc79..6fc299a0d 100644
--- a/fe/src/main/java/org/apache/impala/authorization/AuthorizableColumn.java
+++ b/fe/src/main/java/org/apache/impala/authorization/AuthorizableColumn.java
@@ -25,43 +25,24 @@ import com.google.common.base.Strings;
/**
* A class to authorize access to a column.
*/
-public class AuthorizableColumn extends Authorizable {
- private final String dbName_;
- private final String tableName_;
+public class AuthorizableColumn extends AuthorizableTable {
private final String columnName_;
- // Owner for the parent db or table that this Authorizable corresponds to.
- @Nullable
- private final String ownerUser_;
public AuthorizableColumn(
String dbName, String tableName, String columnName, @Nullable String
ownerUser) {
- Preconditions.checkArgument(!Strings.isNullOrEmpty(dbName));
- Preconditions.checkArgument(!Strings.isNullOrEmpty(tableName));
+ super(dbName, tableName, ownerUser);
Preconditions.checkArgument(!Strings.isNullOrEmpty(columnName));
- dbName_ = dbName;
- tableName_ = tableName;
columnName_ = columnName;
- ownerUser_ = ownerUser;
}
@Override
- public String getName() { return dbName_ + "." + tableName_ + "." +
columnName_; }
+ public String getName() {
+ return getDbName() + "." + getTableName() + "." + columnName_;
+ }
@Override
public Type getType() { return Type.COLUMN; }
- @Override
- public String getFullTableName() { return dbName_ + "." + tableName_; }
-
- @Override
- public String getDbName() { return dbName_; }
-
- @Override
- public String getTableName() { return tableName_; }
-
@Override
public String getColumnName() { return columnName_; }
-
- @Override
- public String getOwnerUser() { return ownerUser_; }
}
diff --git
a/fe/src/main/java/org/apache/impala/authorization/AuthorizableTable.java
b/fe/src/main/java/org/apache/impala/authorization/AuthorizableTable.java
index 3cfc8ad86..66544e044 100644
--- a/fe/src/main/java/org/apache/impala/authorization/AuthorizableTable.java
+++ b/fe/src/main/java/org/apache/impala/authorization/AuthorizableTable.java
@@ -52,7 +52,7 @@ public class AuthorizableTable extends Authorizable {
public String getTableName() { return tableName_; }
@Override
- public String getFullTableName() { return getName(); }
+ public String getFullTableName() { return getDbName() + "." +
getTableName(); }
@Override
public String getOwnerUser() { return ownerUser_; }
diff --git
a/fe/src/main/java/org/apache/impala/authorization/BaseAuthorizationChecker.java
b/fe/src/main/java/org/apache/impala/authorization/BaseAuthorizationChecker.java
index b2b3980cc..9e255e370 100644
---
a/fe/src/main/java/org/apache/impala/authorization/BaseAuthorizationChecker.java
+++
b/fe/src/main/java/org/apache/impala/authorization/BaseAuthorizationChecker.java
@@ -19,6 +19,7 @@ package org.apache.impala.authorization;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
import org.apache.impala.analysis.AnalysisContext.AnalysisResult;
import org.apache.impala.analysis.Analyzer;
import org.apache.impala.authorization.Authorizable.Type;
@@ -48,6 +49,10 @@ public abstract class BaseAuthorizationChecker implements
AuthorizationChecker {
private final static Logger LOG = LoggerFactory.getLogger(
BaseAuthorizationChecker.class);
+ private final static Set<Privilege> ALLOWED_HIER_AUTHZ_TABLE_PRIVILEGES =
+ ImmutableSet.of(Privilege.SELECT, Privilege.INSERT, Privilege.CREATE,
+ Privilege.ALL, Privilege.ALTER, Privilege.VIEW_METADATA);
+
protected final AuthorizationConfig config_;
/**
@@ -130,32 +135,51 @@ public abstract class BaseAuthorizationChecker implements
AuthorizationChecker {
// has column-level privilege request. The hierarchical nature requires
special
// logic to process correctly and efficiently.
if (analysisResult.isHierarchicalAuthStmt()) {
- // Map of table name to a list of privilege requests associated with
that table.
- // These include both table-level and column-level privilege requests.
We use a
- // LinkedHashMap to preserve the order in which requests are inserted.
+ // Map of table name to a list of table-level privilege requests
associated with
+ // that table.
Map<String, List<PrivilegeRequest>> tablePrivReqs = new
LinkedHashMap<>();
+ // Map of table name to a list of column-level privilege requests
associated with
+ // that table.
+ Map<String, List<PrivilegeRequest>> columnPrivReqs = new
LinkedHashMap<>();
// Privilege requests that are not column or table-level.
List<PrivilegeRequest> otherPrivReqs = new ArrayList<>();
- // Group the registered privilege requests based on the table they
reference.
+ // Group the registered privilege requests based on the type of the
Authorizable
+ // and the table they reference.
for (PrivilegeRequest privReq : analyzer.getPrivilegeReqs()) {
String tableName = privReq.getAuthorizable().getFullTableName();
if (tableName == null) {
otherPrivReqs.add(privReq);
} else {
- List<PrivilegeRequest> requests = tablePrivReqs.get(tableName);
- if (requests == null) {
- requests = new ArrayList<>();
- tablePrivReqs.put(tableName, requests);
+ List<PrivilegeRequest> requests;
+ if (privReq.getAuthorizable().getType() == Authorizable.Type.TABLE) {
+ // We allow the ALL privilege because for the UPSERT operation
against Kudu
+ // tables, we set the required privilege to ALL since we don't
have an UPSERT
+ // privilege yet. Refer to InsertStmt#analyzeTargetTable().
+ // CREATE privilege would be registered in
CreateTableAsSelectStmt#analyze().
+ // In addition, VIEW_METADATA privilege would be registered in
+ // CopyTestCaseStmt#analyze().
+ Preconditions.checkState(ALLOWED_HIER_AUTHZ_TABLE_PRIVILEGES
+ .contains(privReq.getPrivilege()));
+ requests = tablePrivReqs.computeIfAbsent(tableName, k -> new
ArrayList<>());
+ requests.add(privReq);
+ } else {
+ Preconditions.checkState(privReq.getAuthorizable().getType() ==
Type.COLUMN);
+ requests = columnPrivReqs.computeIfAbsent(tableName, k -> new
ArrayList<>());
+ requests.add(privReq);
}
- // The table-level SELECT must be the first table-level request, and
it
- // must precede all column-level privilege requests.
- Preconditions.checkState((requests.isEmpty() ||
- !(privReq.getAuthorizable().getType() ==
Authorizable.Type.COLUMN)) ||
- (requests.get(0).getAuthorizable().getType() ==
Authorizable.Type.TABLE &&
- requests.get(0).getPrivilege() == Privilege.SELECT));
- requests.add(privReq);
}
}
+ // The following makes sure each column-level privilege request has at
least one
+ // corresponding table-level privilege request.
+ // For each list of column-level privilege requests associated with a
table, add
+ // the requests to the respective list of table-level privilege requests
so that
+ // entries in 'tablePrivReqs' could be used to authorize table accesses
in
+ // authorizeTableAccess() below.
+ for (Map.Entry<String, List<PrivilegeRequest>> entry :
columnPrivReqs.entrySet()) {
+ List<PrivilegeRequest> privReqs = tablePrivReqs.get(entry.getKey());
+ Preconditions.checkState(privReqs != null);
+ privReqs.addAll(entry.getValue());
+ }
// Check any non-table, non-column privilege requests first.
for (PrivilegeRequest request : otherPrivReqs) {
@@ -238,29 +262,36 @@ public abstract class BaseAuthorizationChecker implements
AuthorizationChecker {
// not want Impala to show any information when these features are
disabled.
authorizeRowFilterAndColumnMask(analyzer.getUser(), requests);
- boolean hasTableSelectPriv = true;
- boolean hasColumnSelectPriv = false;
+ boolean hasTableAccessPriv = true;
+ boolean hasColumnAccessPriv = false;
for (PrivilegeRequest request: requests) {
if (request.getAuthorizable().getType() == Authorizable.Type.TABLE) {
try {
authorizePrivilegeRequest(authzCtx, analysisResult, catalog,
request);
} catch (AuthorizationException e) {
// Authorization fails if we fail to authorize any table-level
request that is
- // not a SELECT privilege (e.g. INSERT).
- if (request.getPrivilege() != Privilege.SELECT) throw e;
- hasTableSelectPriv = false;
+ // neither the SELECT nor INSERT privileges.
+ // For UPSERT against a Kudu table, since we set the required
privilege to ALL,
+ // we would throw an AuthorizationException if the requesting user
does not
+ // have the ALL privilege on the Kudu table.
+ // For CTAS, the requesting user is required to have the CREATE
privilege on
+ // the target table, so we would throw an AuthorizationException too
if the
+ // requesting user does not have the required privilege.
+ if (request.getPrivilege() != Privilege.SELECT &&
+ request.getPrivilege() != Privilege.INSERT) throw e;
+ hasTableAccessPriv = false;
}
} else {
Preconditions.checkState(
request.getAuthorizable().getType() == Authorizable.Type.COLUMN);
// In order to support deny policies on columns
- if (hasTableSelectPriv &&
+ if (hasTableAccessPriv &&
request.getPrivilege() != Privilege.SELECT &&
request.getPrivilege() != Privilege.INSERT) {
continue;
}
if (hasAccess(authzCtx, analyzer.getUser(), request)) {
- hasColumnSelectPriv = true;
+ hasColumnAccessPriv = true;
continue;
}
// Make sure we don't reveal any column names in the error message.
@@ -270,9 +301,10 @@ public abstract class BaseAuthorizationChecker implements
AuthorizationChecker {
request.getAuthorizable().getFullTableName()));
}
}
- if (!hasTableSelectPriv && !hasColumnSelectPriv) {
+ if (!hasTableAccessPriv && !hasColumnAccessPriv) {
throw new AuthorizationException(String.format("User '%s' does not have
" +
- "privileges to execute 'SELECT' on: %s",
analyzer.getUser().getName(),
+ "privileges to execute '%s' on: %s",
analyzer.getUser().getName(),
+ requests.get(0).getPrivilege().toString(),
requests.get(0).getAuthorizable().getFullTableName()));
}
}
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 207303003..7c02dc483 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
@@ -25,7 +25,6 @@ import org.apache.hadoop.security.UserGroupInformation;
import org.apache.impala.analysis.AnalysisContext.AnalysisResult;
import org.apache.impala.authorization.Authorizable;
import org.apache.impala.authorization.Authorizable.Type;
-import org.apache.impala.authorization.AuthorizableTable;
import org.apache.impala.authorization.AuthorizationChecker;
import org.apache.impala.authorization.AuthorizationConfig;
import org.apache.impala.authorization.AuthorizationContext;
@@ -46,6 +45,7 @@ import org.apache.ranger.plugin.model.RangerPolicy;
import org.apache.ranger.plugin.model.RangerServiceDef;
import org.apache.ranger.plugin.policyengine.RangerAccessRequest;
import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl;
+import org.apache.ranger.plugin.policyengine.RangerAccessResource;
import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl;
import org.apache.ranger.plugin.policyengine.RangerAccessResult;
import org.apache.ranger.plugin.policyengine.RangerPolicyEngine;
@@ -284,17 +284,24 @@ public class RangerAuthorizationChecker extends
BaseAuthorizationChecker {
throws AuthorizationException, InternalException {
RangerAuthorizationContext originalCtx = (RangerAuthorizationContext)
authzCtx;
RangerBufferAuditHandler originalAuditHandler =
originalCtx.getAuditHandler();
- // case 1: table (select) OK, columns (select) OK --> add the table event,
- // add the column events
- // case 2: table (non-select) ERROR --> add the table event
- // case 3: table (select) ERROR, columns (select) OK -> only add the
column events
- // case 4: table (select) ERROR, columns (select) ERROR --> add the table
event
- // case 5: table (select) ERROR --> add the table event
+ // For access_type in {select, insert}, the following holds.
+ // case 1: table (access_type) OK, columns (access_type) OK --> add the
table event,
+ // add the column events
+ // case 2: table (non-select and non-insert) ERROR --> add the table event
+ // This could happen in upsert against a Kudu table if the
requesting user
+ // does not have the all privilege on the target table.
+ // case 3: table (access_type) ERROR, columns (access_type) OK -> only add
the column
+ // events
+ // case 4: table (access_type) ERROR, columns (access_type) ERROR --> add
the table
+ // event
+ // case 5: table (access_type) ERROR --> add the table event
// This could happen when the select request for a non-existing
table fails
// the authorization.
- // case 6: table (select) OK, columns (select) ERROR --> add the first
column event
- // This could happen when the requesting user is granted the
select privilege
- // on the table but is denied access to a column in the same table
in Ranger.
+ // case 6: table (access_type) OK, columns (access_type) ERROR --> add the
first
+ // column event
+ // This could happen when the requesting user is granted the
select or insert
+ // privileges on the table but is denied access to a column in the
same table
+ // in Ranger.
RangerAuthorizationContext tmpCtx = new RangerAuthorizationContext(
originalCtx.getSessionState(), originalCtx.getTimeline());
tmpCtx.setAuditHandler(new RangerBufferAuditHandler(originalAuditHandler));
@@ -305,8 +312,10 @@ public class RangerAuthorizationChecker extends
BaseAuthorizationChecker {
authorizationException = e;
tmpCtx.getAuditHandler().getAuthzEvents().stream()
.filter(evt ->
- // case 2: get the first failing non-select table
- (!"select".equalsIgnoreCase(evt.getAccessType()) &&
+ // case 2: get the first failing table event that is neither
select nor
+ // insert.
+ ((!"select".equalsIgnoreCase(evt.getAccessType()) &&
+ !"insert".equalsIgnoreCase(evt.getAccessType())) &&
"@table".equals(evt.getResourceType())) ||
// case 4 & 5 & 6: get the table or a column event
(("@table".equals(evt.getResourceType()) ||
@@ -719,21 +728,39 @@ public class RangerAuthorizationChecker extends
BaseAuthorizationChecker {
Preconditions.checkState(accessResult.getIsAllowed(),
"update should be allowed before checking this");
String originalAccessType = request.getAccessType();
+ RangerAccessResource originalResource = null;
// Row-filtering/Column-masking policies are defined only for SELECT
requests.
request.setAccessType(SELECT_ACCESS_TYPE);
+ // This if block allows us to correctly deny the INSERT request when there
is any row
+ // filtering policy defined on the table against the requesting user.
+ // Recall that a user is allowed to execute the INSERT statement as long
as the user
+ // is allowed to insert values into each target column, even though the
user is not
+ // allowed to insert values into the entire table.
+ // Therefore, when determining whether the requesting user is allowed to
insert
+ // values into a target column, we need to temporarily set the resource to
the table
+ // to which the column belongs. Without us doing so,
+ // rowFilterResult.isRowFilterEnabled() will return false and thus the
requesting
+ // user will not be blocked.
+ if (authorizable.getType() == Type.COLUMN) {
+ originalResource = request.getResource();
+ RangerAccessResourceImpl derivedTableResource = new
RangerImpalaResourceBuilder()
+ .database(authorizable.getDbName())
+ .table(authorizable.getTableName())
+ .owner(authorizable.getOwnerUser())
+ .build();
+ request.setResource(derivedTableResource);
+ }
// Check if row filtering is enabled for the table/view.
- if (authorizable.getType() == Type.TABLE) {
- RangerAccessResult rowFilterResult = plugin_.evalRowFilterPolicies(
- request, /*resultProcessor*/null);
- if (rowFilterResult != null && rowFilterResult.isRowFilterEnabled()) {
- LOG.info("Deny {} on {} due to row filtering policy {}",
- privilege, authorizable.getName(), rowFilterResult.getPolicyId());
- accessResult.setIsAllowed(false);
- accessResult.setPolicyId(rowFilterResult.getPolicyId());
- accessResult.setReason("User does not have access to all rows of the
table");
- } else {
- LOG.trace("No row filtering policy found on {}.",
authorizable.getName());
- }
+ RangerAccessResult rowFilterResult = plugin_.evalRowFilterPolicies(
+ request, /*resultProcessor*/null);
+ if (rowFilterResult != null && rowFilterResult.isRowFilterEnabled()) {
+ LOG.info("Deny {} on {} due to row filtering policy {}",
+ privilege, authorizable.getName(), rowFilterResult.getPolicyId());
+ accessResult.setIsAllowed(false);
+ accessResult.setPolicyId(rowFilterResult.getPolicyId());
+ accessResult.setReason("User does not have access to all rows of the
table");
+ } else {
+ LOG.trace("No row filtering policy found on {}.",
authorizable.getName());
}
// Check if masking is enabled for any column in the table/view.
if (accessResult.getIsAllowed()) {
@@ -751,6 +778,9 @@ public class RangerAuthorizationChecker extends
BaseAuthorizationChecker {
// Set back the original access type. The request object is still
referenced by the
// access result.
request.setAccessType(originalAccessType);
+ if (originalResource != null) {
+ request.setResource(originalResource);
+ }
// Only add deny audits.
if (!accessResult.getIsAllowed() && auditHandler != null) {
auditHandler.processResult(accessResult);
diff --git
a/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationContext.java
b/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationContext.java
index 5c918f976..3ac694064 100644
---
a/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationContext.java
+++
b/fe/src/main/java/org/apache/impala/authorization/ranger/RangerAuthorizationContext.java
@@ -113,10 +113,10 @@ public class RangerAuthorizationContext extends
AuthorizationContext {
String databaseName = parsedNames[0];
String tableName = parsedNames[1];
String columnName = parsedNames[2];
- // Currently the access type can only be "select" for a non-column
masking audit
- // log entry. We include the access type as part of the key to avoid
accidentally
- // combining audit log entries corresponding to the same column but of
different
- // access types in the future.
+ // Currently the access type can only be "select" or "insert" for a
non-column
+ // masking audit log entry. We include the access type as part of the
key to
+ // avoid accidentally combining audit log entries corresponding to the
same
+ // column but of different access types in the future.
String accessType = event.getAccessType();
String key = policyId + "/" + databaseName + "/" + tableName + "/" +
accessType;
if (!consolidatedEvents.containsKey(key)) {
diff --git
a/fe/src/test/java/org/apache/impala/analysis/AnalyzeAuthStmtsTest.java
b/fe/src/test/java/org/apache/impala/analysis/AnalyzeAuthStmtsTest.java
index 727c510ae..79c48af92 100644
--- a/fe/src/test/java/org/apache/impala/analysis/AnalyzeAuthStmtsTest.java
+++ b/fe/src/test/java/org/apache/impala/analysis/AnalyzeAuthStmtsTest.java
@@ -259,12 +259,11 @@ public class AnalyzeAuthStmtsTest extends
FrontendTestBase {
AnalysisError(String.format("%s SELECT () ON TABLE functional.alltypes
" +
"%s %s", formatArgs), "Empty column list in column privilege
spec.");
// INSERT/ALL privileges on columns
- AnalysisError(String.format("%s INSERT (id, tinyint_col) ON TABLE " +
- "functional.alltypes %s %s", formatArgs), "Only 'SELECT'
privileges " +
- "are allowed in a column privilege spec.");
+ AnalyzesOk(String.format("%s INSERT (id, tinyint_col) ON TABLE " +
+ "functional.alltypes %s %s", formatArgs));
AnalysisError(String.format("%s ALL (id, tinyint_col) ON TABLE " +
- "functional.alltypes %s %s", formatArgs), "Only 'SELECT'
privileges " +
- "are allowed in a column privilege spec.");
+ "functional.alltypes %s %s", formatArgs), "Only 'SELECT' and
'INSERT' " +
+ "privileges are allowed in a column privilege spec.");
// Columns/table that don't exist
AnalysisError(String.format("%s SELECT (invalid_col) ON TABLE " +
"functional.alltypes %s %s", formatArgs), "Error setting/showing "
+
diff --git
a/fe/src/test/java/org/apache/impala/authorization/AuthorizationStmtTest.java
b/fe/src/test/java/org/apache/impala/authorization/AuthorizationStmtTest.java
index edc71d82c..70dc27ef7 100644
---
a/fe/src/test/java/org/apache/impala/authorization/AuthorizationStmtTest.java
+++
b/fe/src/test/java/org/apache/impala/authorization/AuthorizationStmtTest.java
@@ -166,7 +166,11 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
verifyPrivilegeReqs("select a.id from functional.alltypes a",
expectedAuthorizables);
// Insert.
- expectedAuthorizables = Sets.newHashSet("functional.alltypes");
+ expectedAuthorizables = Sets.newHashSet(
+ "functional.alltypes",
+ "functional.alltypes.id",
+ "functional.alltypes.month",
+ "functional.alltypes.year");
verifyPrivilegeReqs("insert into functional.alltypes(id) partition(month,
year) " +
"values(1, 1, 2018)", expectedAuthorizables);
verifyPrivilegeReqs(createAnalysisCtx("functional"),
@@ -174,7 +178,9 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
expectedAuthorizables);
// Insert with constant select.
- expectedAuthorizables = Sets.newHashSet("functional.zipcode_incomes");
+ expectedAuthorizables = Sets.newHashSet(
+ "functional.zipcode_incomes",
+ "functional.zipcode_incomes.id");
verifyPrivilegeReqs("insert into functional.zipcode_incomes(id) select
'123'",
expectedAuthorizables);
verifyPrivilegeReqs(createAnalysisCtx("functional"),
@@ -210,7 +216,7 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
expectedAuthorizables);
// Show tables.
- expectedAuthorizables = Sets.newHashSet("functional.*.*");
+ expectedAuthorizables = Sets.newHashSet("functional");
verifyPrivilegeReqs("show tables in functional", expectedAuthorizables);
verifyPrivilegeReqs(createAnalysisCtx("functional"), "show tables",
expectedAuthorizables);
@@ -304,7 +310,10 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
"update alltypes set int_col = 1", expectedAuthorizables);
// Upsert table.
- expectedAuthorizables = Sets.newHashSet("functional_kudu.alltypes");
+ expectedAuthorizables = Sets.newHashSet(
+ "functional_kudu.alltypes",
+ "functional_kudu.alltypes.id",
+ "functional_kudu.alltypes.int_col");
verifyPrivilegeReqs("upsert into table functional_kudu.alltypes(id,
int_col) " +
"values(1, 1)", expectedAuthorizables);
verifyPrivilegeReqs(createAnalysisCtx("functional_kudu"),
@@ -920,6 +929,9 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
.ok(onTable("functional", "zipcode_incomes", TPrivilegeLevel.ALL))
.ok(onTable("functional", "zipcode_incomes", TPrivilegeLevel.OWNER))
.ok(onTable("functional", "zipcode_incomes", TPrivilegeLevel.INSERT))
+ .ok(onColumn("functional", "zipcode_incomes", "id",
TPrivilegeLevel.ALL))
+ .ok(onColumn("functional", "zipcode_incomes", "id",
TPrivilegeLevel.OWNER))
+ .ok(onColumn("functional", "zipcode_incomes", "id",
TPrivilegeLevel.INSERT))
.error(insertError("functional.zipcode_incomes"))
.error(insertError("functional.zipcode_incomes"), onServer(allExcept(
TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER,
TPrivilegeLevel.INSERT)))
@@ -928,7 +940,10 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
TPrivilegeLevel.INSERT)))
.error(insertError("functional.zipcode_incomes"),
onTable("functional",
"zipcode_incomes", allExcept(TPrivilegeLevel.ALL,
TPrivilegeLevel.OWNER,
- TPrivilegeLevel.INSERT)));
+ TPrivilegeLevel.INSERT)))
+ .error(insertError("functional.zipcode_incomes"),
onColumn("functional",
+ "zipcode_incomes", "id", allExcept(TPrivilegeLevel.ALL,
+ TPrivilegeLevel.OWNER, TPrivilegeLevel.INSERT)));
}
for (AuthzTest test: new AuthzTest[]{
@@ -947,13 +962,25 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
.ok(onDatabase("functional", TPrivilegeLevel.INSERT,
TPrivilegeLevel.SELECT))
.ok(onTable("functional", "alltypes", TPrivilegeLevel.ALL),
onTable("functional", "alltypestiny", TPrivilegeLevel.ALL))
+ .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS,
TPrivilegeLevel.ALL),
+ onTable("functional", "alltypestiny", TPrivilegeLevel.ALL))
.ok(onTable("functional", "alltypes", TPrivilegeLevel.OWNER),
onTable("functional", "alltypestiny", TPrivilegeLevel.OWNER))
+ .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS,
+ TPrivilegeLevel.OWNER),
+ onTable("functional", "alltypestiny", TPrivilegeLevel.OWNER))
.ok(onTable("functional", "alltypes", TPrivilegeLevel.INSERT),
onTable("functional", "alltypestiny", TPrivilegeLevel.SELECT))
+ .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS,
+ TPrivilegeLevel.INSERT),
+ onTable("functional", "alltypestiny", TPrivilegeLevel.SELECT))
.ok(onTable("functional", "alltypes", TPrivilegeLevel.INSERT),
onColumn("functional", "alltypestiny", ALLTYPES_COLUMNS,
TPrivilegeLevel.SELECT))
+ .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS,
+ TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypestiny", ALLTYPES_COLUMNS,
+ TPrivilegeLevel.SELECT))
.error(selectError("functional.alltypestiny"))
.error(selectError("functional.alltypestiny"), onServer(allExcept(
TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER,
TPrivilegeLevel.INSERT,
@@ -965,6 +992,10 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
"alltypestiny", TPrivilegeLevel.SELECT), onTable("functional",
"alltypes", allExcept(TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER,
TPrivilegeLevel.INSERT)))
+ .error(insertError("functional.alltypes"), onTable("functional",
+ "alltypestiny", TPrivilegeLevel.SELECT), onColumn("functional",
+ "alltypes", ALLTYPES_COLUMNS, allExcept(TPrivilegeLevel.ALL,
+ TPrivilegeLevel.OWNER, TPrivilegeLevel.INSERT)))
.error(selectError("functional.alltypestiny"), onTable("functional",
"alltypestiny", allExcept(TPrivilegeLevel.ALL,
TPrivilegeLevel.OWNER,
TPrivilegeLevel.SELECT)), onTable("functional", "alltypes",
@@ -982,13 +1013,22 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
.ok(onDatabase("functional", TPrivilegeLevel.INSERT,
TPrivilegeLevel.SELECT))
.ok(onTable("functional", "alltypes", TPrivilegeLevel.ALL),
onTable("functional", "alltypes_view", TPrivilegeLevel.ALL))
+ .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS,
TPrivilegeLevel.ALL),
+ onTable("functional", "alltypes_view", TPrivilegeLevel.ALL))
.ok(onTable("functional", "alltypes", TPrivilegeLevel.OWNER),
onTable("functional", "alltypes_view", TPrivilegeLevel.OWNER))
+ .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS,
TPrivilegeLevel.OWNER),
+ onTable("functional", "alltypes_view", TPrivilegeLevel.OWNER))
.ok(onTable("functional", "alltypes", TPrivilegeLevel.INSERT),
onTable("functional", "alltypes_view", TPrivilegeLevel.SELECT))
+ .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS,
TPrivilegeLevel.INSERT),
+ onTable("functional", "alltypes_view", TPrivilegeLevel.SELECT))
.ok(onTable("functional", "alltypes", TPrivilegeLevel.INSERT),
onColumn("functional", "alltypes_view", ALLTYPES_COLUMNS,
TPrivilegeLevel.SELECT))
+ .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS,
TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes_view", ALLTYPES_COLUMNS,
+ TPrivilegeLevel.SELECT))
.error(selectError("functional.alltypes_view"))
.error(selectError("functional.alltypes_view"), onServer(allExcept(
TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER, TPrivilegeLevel.INSERT,
@@ -1000,6 +1040,10 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
"alltypes_view", TPrivilegeLevel.SELECT), onTable("functional",
"alltypes", allExcept(TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER,
TPrivilegeLevel.INSERT)))
+ .error(insertError("functional.alltypes"), onTable("functional",
+ "alltypes_view", TPrivilegeLevel.SELECT), onColumn("functional",
+ "alltypes", ALLTYPES_COLUMNS, allExcept(TPrivilegeLevel.ALL,
+ TPrivilegeLevel.OWNER, TPrivilegeLevel.INSERT)))
.error(selectError("functional.alltypes_view"), onTable("functional",
"alltypes_view", allExcept(TPrivilegeLevel.ALL,
TPrivilegeLevel.OWNER,
TPrivilegeLevel.SELECT)),
@@ -1018,12 +1062,21 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
.ok(onTable("functional", "alltypes", TPrivilegeLevel.ALL),
onTable("functional", "alltypesagg", TPrivilegeLevel.ALL),
onTable("functional", "alltypestiny", TPrivilegeLevel.ALL))
+ .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS,
TPrivilegeLevel.ALL),
+ onTable("functional", "alltypesagg", TPrivilegeLevel.ALL),
+ onTable("functional", "alltypestiny", TPrivilegeLevel.ALL))
.ok(onTable("functional", "alltypes", TPrivilegeLevel.OWNER),
onTable("functional", "alltypesagg", TPrivilegeLevel.OWNER),
onTable("functional", "alltypestiny", TPrivilegeLevel.OWNER))
+ .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS,
TPrivilegeLevel.OWNER),
+ onTable("functional", "alltypesagg", TPrivilegeLevel.OWNER),
+ onTable("functional", "alltypestiny", TPrivilegeLevel.OWNER))
.ok(onTable("functional", "alltypes", TPrivilegeLevel.INSERT),
onTable("functional", "alltypesagg", TPrivilegeLevel.SELECT),
onTable("functional", "alltypestiny", TPrivilegeLevel.SELECT))
+ .ok(onColumn("functional", "alltypes", ALLTYPES_COLUMNS,
TPrivilegeLevel.INSERT),
+ onTable("functional", "alltypesagg", TPrivilegeLevel.SELECT),
+ onTable("functional", "alltypestiny", TPrivilegeLevel.SELECT))
.error(selectError("functional.alltypesagg"))
.error(selectError("functional.alltypesagg"), onServer(allExcept(
TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER, TPrivilegeLevel.INSERT,
@@ -1036,6 +1089,11 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
"alltypestiny", TPrivilegeLevel.SELECT), onTable("functional",
"alltypes", allExcept(TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER,
TPrivilegeLevel.INSERT)))
+ .error(insertError("functional.alltypes"), onTable("functional",
+ "alltypesagg", TPrivilegeLevel.SELECT), onTable("functional",
+ "alltypestiny", TPrivilegeLevel.SELECT), onColumn("functional",
+ "alltypes", ALLTYPES_COLUMNS, allExcept(TPrivilegeLevel.ALL,
+ TPrivilegeLevel.OWNER, TPrivilegeLevel.INSERT)))
.error(selectError("functional.alltypesagg"), onTable("functional",
"alltypesagg", allExcept(TPrivilegeLevel.ALL,
TPrivilegeLevel.OWNER,
TPrivilegeLevel.SELECT)),
@@ -1073,7 +1131,7 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
.ok(onTable("functional", "alltypes", privilege))
.ok(onColumn("functional", "alltypes", "id", privilege));
}
- test.error(accessError("functional.*.*"));
+ test.error(accessError("functional"));
// Accessing default database should always be allowed.
authorize("use default").ok();
@@ -1082,7 +1140,7 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
authorize("use _impala_builtins").ok();
// Use a non-existent database.
- authorize("use nodb").error(accessError("nodb.*.*"));
+ authorize("use nodb").error(accessError("nodb"));
}
@Test
@@ -1264,7 +1322,7 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
.ok(onDatabase("functional", privilege))
.ok(onTable("functional", "alltypes", privilege));
}
- test.error(accessError("functional.*.*"));
+ test.error(accessError("functional"));
// Show metadata tables in table.
test = authorize("show metadata tables in
functional_parquet.iceberg_query_metadata");
@@ -1286,7 +1344,7 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
.ok(onDatabase("functional", privilege))
.ok(onTable("functional", "alltypes_views", privilege));
}
- test.error(accessError("functional.*.*"));
+ test.error(accessError("functional"));
// Show functions.
test = authorize("show functions in functional");
@@ -1829,7 +1887,7 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
.ok(onDatabase("functional"), onDatabase("functional",
TPrivilegeLevel.CREATE,
TPrivilegeLevel.INSERT), onColumn("functional", "alltypes",
"int_col",
TPrivilegeLevel.OWNER))
- .error(createError("functional"))
+ .error(accessError("functional"))
.error(createError("functional"), onServer(allExcept(
TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER,
TPrivilegeLevel.CREATE,
TPrivilegeLevel.INSERT, TPrivilegeLevel.SELECT)))
@@ -2019,7 +2077,7 @@ public class AuthorizationStmtTest extends
AuthorizationTestBase {
.ok(onServer(TPrivilegeLevel.OWNER))
.ok(onDatabase("functional", TPrivilegeLevel.CREATE,
TPrivilegeLevel.INSERT,
TPrivilegeLevel.SELECT))
- .error(createError("functional"))
+ .error(accessError("functional"))
.error(createError("functional"), onServer(allExcept(TPrivilegeLevel.ALL,
TPrivilegeLevel.OWNER, TPrivilegeLevel.CREATE,
TPrivilegeLevel.INSERT,
TPrivilegeLevel.SELECT)))
diff --git
a/fe/src/test/java/org/apache/impala/authorization/AuthorizationTestBase.java
b/fe/src/test/java/org/apache/impala/authorization/AuthorizationTestBase.java
index 480391c26..96c3c7940 100644
---
a/fe/src/test/java/org/apache/impala/authorization/AuthorizationTestBase.java
+++
b/fe/src/test/java/org/apache/impala/authorization/AuthorizationTestBase.java
@@ -654,6 +654,46 @@ public abstract class AuthorizationTestBase extends
FrontendTestBase {
LOG.info("Deleted ranger policy {}", policyName);
}
+ protected String createJsonDenyPolicy(String policyName, String databaseName,
+ String tableName, String columnName, String accessType, String user) {
+ String json = String.format("{\n" +
+ " \"name\": \"%s\",\n" +
+ " \"policyType\": 0,\n" +
+ " \"serviceType\": \"%s\",\n" +
+ " \"service\": \"%s\",\n" +
+ " \"resources\": {\n" +
+ " \"database\": {\n" +
+ " \"values\": [\"%s\"],\n" +
+ " \"isExcludes\": false,\n" +
+ " \"isRecursive\": false\n" +
+ " },\n" +
+ " \"table\": {\n" +
+ " \"values\": [\"%s\"],\n" +
+ " \"isExcludes\": false,\n" +
+ " \"isRecursive\": false\n" +
+ " },\n" +
+ " \"column\": {\n" +
+ " \"values\": [\"%s\"],\n" +
+ " \"isExcludes\": false,\n" +
+ " \"isRecursive\": false\n" +
+ " }\n" +
+ " },\n" +
+ " \"denyPolicyItems\": [\n" +
+ " {\n" +
+ " \"accesses\": [\n" +
+ " {\n" +
+ " \"type\": \"%s\",\n" +
+ " \"isAllowed\": true\n" +
+ " }\n" +
+ " ],\n" +
+ " \"users\": [\"%s\"]\n" +
+ " }\n" +
+ " ]\n" +
+ "}", policyName, RANGER_SERVICE_TYPE, RANGER_SERVICE_NAME,
databaseName,
+ tableName, columnName, accessType, user);
+ return json;
+ }
+
// Convert TDescribeResult to list of strings.
private static List<String> resultToStringList(TDescribeResult result) {
List<String> list = new ArrayList<>();
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 7dd779ef3..0130f944c 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
@@ -112,7 +112,7 @@ public class RangerAuditLogTest extends
AuthorizationTestBase {
onUri("hdfs://localhost:20500/test-warehouse/new_table",
TPrivilegeLevel.ALL));
authzOk(events -> {
- // Table event and 2 column events
+ // A table event and 1 consolidated column event.
assertEquals(2, events.size());
assertEventEquals("@table", "select", "functional/alltypes", 1,
events.get(0));
assertEventEquals("@column",
"select","functional/alltypes/id,string_col", 1,
@@ -122,6 +122,18 @@ public class RangerAuditLogTest extends
AuthorizationTestBase {
}, "select id, string_col from functional.alltypes",
onTable("functional", "alltypes", TPrivilegeLevel.SELECT));
+ authzOk(events -> {
+ // A table event and 1 consolidated column event.
+ assertEquals(2, events.size());
+ assertEventEquals("@table", "insert", "functional/alltypes", 1,
events.get(0));
+ assertEventEquals("@column", "insert",
+ "functional/alltypes/id,string_col,year,month", 1, events.get(1));
+ assertEquals("insert into functional.alltypes (id, string_col) partition
" +
+ "(year, month) values (1, \"abc\", 2026, 12)",
events.get(0).getRequestData());
+ }, "insert into functional.alltypes (id, string_col) partition " +
+ "(year, month) values (1, \"abc\", 2026, 12)",
+ onTable("functional", "alltypes", TPrivilegeLevel.INSERT));
+
// Audit log entries are not consolidated since each of them corresponds
to a
// different policy.
authzOk(events -> {
@@ -169,6 +181,76 @@ public class RangerAuditLogTest extends
AuthorizationTestBase {
onColumn("functional", "alltypes", "year", TPrivilegeLevel.SELECT),
onColumn("functional", "alltypes", "month", TPrivilegeLevel.SELECT));
+ // Audit log entries are not consolidated since each of them corresponds
to a
+ // different policy.
+ // In this query, we insert data into each column of the table.
+ authzOk(events -> {
+ assertEquals(13, events.size());
+ assertEventEquals("@column", "insert",
+ "functional/alltypes/id", 1, events.get(0));
+ assertEventEquals("@column", "insert",
+ "functional/alltypes/bool_col", 1, events.get(1));
+ assertEventEquals("@column", "insert",
+ "functional/alltypes/tinyint_col", 1, events.get(2));
+ assertEventEquals("@column", "insert",
+ "functional/alltypes/smallint_col", 1, events.get(3));
+ assertEventEquals("@column", "insert",
+ "functional/alltypes/int_col", 1, events.get(4));
+ assertEventEquals("@column", "insert",
+ "functional/alltypes/bigint_col", 1, events.get(5));
+ assertEventEquals("@column", "insert",
+ "functional/alltypes/float_col", 1, events.get(6));
+ assertEventEquals("@column", "insert",
+ "functional/alltypes/double_col", 1, events.get(7));
+ assertEventEquals("@column", "insert",
+ "functional/alltypes/date_string_col", 1, events.get(8));
+ assertEventEquals("@column", "insert",
+ "functional/alltypes/string_col", 1, events.get(9));
+ assertEventEquals("@column", "insert",
+ "functional/alltypes/timestamp_col", 1, events.get(10));
+ assertEventEquals("@column", "insert",
+ "functional/alltypes/year", 1, events.get(11));
+ assertEventEquals("@column", "insert",
+ "functional/alltypes/month", 1, events.get(12));
+ assertEquals("insert into functional.alltypes partition (year, month) " +
+ "values (0, true, 0, 0, 0, 0, 0.0, 0.0, '12/31/26', '0'," +
+ "'2026-12-31 00:00:00', 2026, 12)", events.get(0).getRequestData());
+ }, "insert into functional.alltypes partition (year, month) " +
+ "values (0, true, 0, 0, 0, 0, 0.0, 0.0, '12/31/26', '0'," +
+ "'2026-12-31 00:00:00', 2026, 12)",
+ onColumn("functional", "alltypes", "id", TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "bool_col", TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "tinyint_col",
TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "smallint_col",
TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "int_col", TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "bigint_col",
TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "float_col",
TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "double_col",
TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "date_string_col",
TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "string_col",
TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "timestamp_col",
TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "year", TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "month", TPrivilegeLevel.INSERT));
+
+ // Audit log entries are consolidated since every column is covered by the
same
+ // policy.
+ authzOk(events -> {
+ assertEquals(1, events.size());
+ assertEventEquals("@column", "insert",
+
"functional/alltypes/id,bool_col,tinyint_col,smallint_col,int_col," +
+
"bigint_col,float_col,double_col,date_string_col,string_col," +
+ "timestamp_col,year,month", 1, events.get(0));
+ assertEquals("insert into functional.alltypes partition (year,
month) " +
+ "values (0, true, 0, 0, 0, 0, 0.0, 0.0, '12/31/26', '0'," +
+ "'2026-12-31 00:00:00', 2026, 12)",
events.get(0).getRequestData());
+ }, "insert into functional.alltypes partition (year, month) " +
+ "values (0, true, 0, 0, 0, 0, 0.0, 0.0, '12/31/26', '0'," +
+ "'2026-12-31 00:00:00', 2026, 12)",
+ // This creates one single Ranger policy covering all columns of the
table.
+ onColumn("functional", "alltypes",
"id,bool_col,tinyint_col,smallint_col," +
+
"int_col,bigint_col,float_col,double_col,date_string_col,string_col," +
+ "timestamp_col,year,month", TPrivilegeLevel.INSERT));
+
authzOk(events -> {
// Only the column events. We don't want to log the failing table event
used for
// short circuiting.
@@ -184,6 +266,30 @@ public class RangerAuditLogTest extends
AuthorizationTestBase {
onColumn("functional", "alltypes", "id", TPrivilegeLevel.SELECT),
onColumn("functional", "alltypes", "string_col",
TPrivilegeLevel.SELECT));
+ String insertQuery = "insert into functional.alltypes (id, string_col)
partition " +
+ "(year, month) values (1, \"abc\", 2026, 12)";
+ authzOk(events -> {
+ // Only the column events. We don't want to log the failing table event
used for
+ // short circuiting.
+ // In this query, we only insert data into 4 columns of the table.
+ assertEquals(4, events.size());
+ assertEventEquals("@column", "insert", "functional/alltypes/id", 1,
events.get(0));
+ assertEquals(insertQuery, events.get(0).getRequestData());
+ assertEventEquals("@column", "insert", "functional/alltypes/string_col",
1,
+ events.get(1));
+ assertEquals(insertQuery, events.get(1).getRequestData());
+ assertEventEquals("@column", "insert", "functional/alltypes/year", 1,
+ events.get(2));
+ assertEquals(insertQuery, events.get(2).getRequestData());
+ assertEventEquals("@column", "insert", "functional/alltypes/month", 1,
+ events.get(3));
+ assertEquals(insertQuery, events.get(3).getRequestData());
+ }, insertQuery,
+ onColumn("functional", "alltypes", "id", TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "string_col",
TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "year", TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "month", TPrivilegeLevel.INSERT));
+
authzOk(events -> {
assertEquals(3, events.size());
assertEventEquals("@udf", "refresh", "*/*", 1, events.get(0));
@@ -302,6 +408,21 @@ public class RangerAuditLogTest extends
AuthorizationTestBase {
assertEventEquals(null, "_ANY", null, 1, events.get(0));
assertEquals("show databases like 'default*'",
events.get(0).getRequestData());
}, "show databases like 'default*'");
+
+ // No audit log entry should be produced for an authorized query against a
+ // non-existing column in an existing table.
+ // Note that for this query, we only register the INSERT privileges on the
columns
+ // 'id' and 'string_col' because 'non_existing_int_col' does not exist,
and thus it
+ // results in an exception before we try to register the privilege
requests for
+ // partitioned columns, i.e., 'year' and 'month'.
+ authzOk(events -> {
+ assertEquals(0, events.size());
+ }, "insert into " +
+ "functional.alltypes (id, string_col, non_existing_int_col) " +
+ "partition (year, month) values (1, \"abc\", 1, 2026, 12)",
+ /* expectAnalysisOk */ false,
+ onColumn("functional", "alltypes", "id", TPrivilegeLevel.INSERT),
+ onColumn("functional", "alltypes", "string_col",
TPrivilegeLevel.INSERT));
}
@Test
@@ -348,6 +469,25 @@ public class RangerAuditLogTest extends
AuthorizationTestBase {
events.get(0).getRequestData());
}, "select id, string_col from functional.alltypes");
+ authzError(events -> {
+ // Only log the table event.
+ assertEquals(1, events.size());
+ assertEventEquals("@table", "insert", "functional/alltypes", 0,
events.get(0));
+ assertEquals("insert into functional.alltypes (id, string_col) partition
" +
+ "(year, month) values (1, \"abc\", 2026, 12)",
events.get(0).getRequestData());
+ }, "insert into functional.alltypes (id, string_col) partition " +
+ "(year, month) values (1, \"abc\", 2026, 12)");
+
+ authzError(events -> {
+ // Only log the table event.
+ // Note that for UPSERT against a Kudu table, we register the ALL
privilege on the
+ // target table.
+ assertEquals(1, events.size());
+ assertEventEquals("@table", "all", "functional_kudu/alltypes", 0,
events.get(0));
+ assertEquals("upsert into table functional_kudu.alltypes (id, int_col) "
+
+ "values (1, 1)", events.get(0).getRequestData());
+ }, "upsert into table functional_kudu.alltypes (id, int_col) values (1,
1)");
+
authzError(events -> {
// Log the table event even when the table does not exist.
assertEquals(1, events.size());
@@ -357,6 +497,30 @@ public class RangerAuditLogTest extends
AuthorizationTestBase {
events.get(0).getRequestData());
}, "select * from functional.non_existing_tbl");
+ authzError(events -> {
+ // Log the table event even when the table does not exist.
+ assertEquals(1, events.size());
+ assertEventEquals("@table", "insert", "functional/non_existing_tbl", 0,
+ events.get(0));
+ assertEquals("insert into functional.non_existing_tbl (id, string_col) "
+
+ "partition (year, month) values (1, \"abc\", 2026, 12)",
+ events.get(0).getRequestData());
+ }, "insert into functional.non_existing_tbl (id, string_col) partition " +
+ "(year, month) values (1, \"abc\", 2026, 12)");
+
+ authzError(events -> {
+ // Log the table event even when a column does not exist in an existing
table.
+ assertEquals(1, events.size());
+ assertEventEquals("@table", "insert", "functional/alltypes", 0,
+ events.get(0));
+ assertEquals("insert into " +
+ "functional.alltypes (id, string_col, non_existing_int_col) " +
+ "partition (year, month) values (1, \"abc\", 1, 2026, 12)",
+ events.get(0).getRequestData());
+ }, "insert into " +
+ "functional.alltypes (id, string_col, non_existing_int_col) " +
+ "partition (year, month) values (1, \"abc\", 1, 2026, 12)");
+
authzError(events -> {
// Show the actual view_metadata audit log.
assertEquals(1, events.size());
@@ -364,6 +528,31 @@ public class RangerAuditLogTest extends
AuthorizationTestBase {
events.get(0));
assertEquals("show partitions functional.alltypes",
events.get(0).getRequestData());
}, "show partitions functional.alltypes");
+
+ String databaseName = "functional";
+ String tableName = "alltypes";
+ String columnName = "id";
+ String policyName = "deny_insert_column";
+ String user = user_.getShortName();
+ String accessType = "update";
+ String json = createJsonDenyPolicy(policyName, databaseName, tableName,
columnName,
+ accessType, user);
+ try {
+ createRangerPolicy(policyName, json);
+ authzError(events -> {
+ // Only log the table event.
+ assertEquals(1, events.size());
+ assertEventEquals("@column", "insert", "functional/alltypes/id", 0,
+ events.get(0));
+ assertEquals("insert into functional.alltypes (id, string_col)
partition " +
+ "(year, month) values (1, \"abc\", 2026, 12)",
+ events.get(0).getRequestData());
+ }, "insert into functional.alltypes (id, string_col) partition " +
+ "(year, month) values (1, \"abc\", 2026, 12)",
+ onTable("functional", "alltypes", TPrivilegeLevel.INSERT));
+ } finally {
+ deleteRangerPolicy(policyName);
+ }
}
@Test
diff --git
a/testdata/workloads/functional-query/queries/QueryTest/grant_revoke.test
b/testdata/workloads/functional-query/queries/QueryTest/grant_revoke.test
index 747891692..40031c73f 100644
--- a/testdata/workloads/functional-query/queries/QueryTest/grant_revoke.test
+++ b/testdata/workloads/functional-query/queries/QueryTest/grant_revoke.test
@@ -201,7 +201,7 @@ does not have privileges to execute 'CREATE' on:
grant_rev_db
---- QUERY
show tables in grant_rev_db
---- CATCH
-does not have privileges to access: grant_rev_db.*
+does not have privileges to access: grant_rev_db
====
---- QUERY
show functions in grant_rev_db
@@ -386,7 +386,7 @@ drop role grant_revoke_test_ALL_TEST_DB;
# Recall that the owner of the database 'grant_rev_db' has been changed to
'admin'.
show tables in grant_rev_db
---- CATCH
-does not have privileges to access: grant_rev_db.*
+does not have privileges to access: grant_rev_db
====
---- QUERY
# The user 'getuser()' can select the data from the table
'grant_rev_db.test_tbl1'
diff --git a/tests/authorization/test_ranger.py
b/tests/authorization/test_ranger.py
index 4c090cf26..458984a7c 100644
--- a/tests/authorization/test_ranger.py
+++ b/tests/authorization/test_ranger.py
@@ -412,7 +412,7 @@ class TestRanger(CustomClusterTestSuite):
"show grant {0} {1} on user_defined_fn {2}.{3}"
.format(kw, id, unique_database, udf), [])
- # Grant column privileges and verify
+ # Grant the select privilege on a column and verify
self._update_privileges_and_verify(
admin_client, "grant select(x) on table {0}.{1} to {2} {3}"
.format(unique_database, unique_table, kw, id),
@@ -421,12 +421,28 @@ class TestRanger(CustomClusterTestSuite):
[kw, id, unique_database, unique_table, "x", "", "", "", "",
"select",
"false"]])
- # Revoke column privileges and verify
+ # Revoke the select privilege on a column and verify
self._update_privileges_and_verify(
admin_client, "revoke select(x) on table {0}.{1} from {2} {3}"
.format(unique_database, unique_table, kw, id),
"show grant {0} {1} on column {2}.{3}.x"
.format(kw, id, unique_database, unique_table), [])
+
+ # Grant the insert privilege on a column and verify
+ self._update_privileges_and_verify(
+ admin_client, "grant insert(x) on table {0}.{1} to {2} {3}"
+ .format(unique_database, unique_table, kw, id),
+ "show grant {0} {1} on column {2}.{3}.x"
+ .format(kw, id, unique_database, unique_table), [
+ [kw, id, unique_database, unique_table, "x", "", "", "", "",
"insert",
+ "false"]])
+
+ # Revoke the insert privilege on a column and verify
+ self._update_privileges_and_verify(
+ admin_client, "revoke insert(x) on table {0}.{1} from {2} {3}"
+ .format(unique_database, unique_table, kw, id),
+ "show grant {0} {1} on column {2}.{3}.x"
+ .format(kw, id, unique_database, unique_table), [])
finally:
admin_client.execute("revoke all on server from {0} {1}".format(kw, id))
admin_client.execute("revoke all on uri '{0}' from {1} {2}"
@@ -447,6 +463,8 @@ class TestRanger(CustomClusterTestSuite):
.format(unique_database, udf, kw, id))
admin_client.execute("revoke select(x) on table {0}.{1} from {2} {3}"
.format(unique_database, unique_table, kw, id))
+ admin_client.execute("revoke insert(x) on table {0}.{1} from {2} {3}"
+ .format(unique_database, unique_table, kw, id))
def _test_show_grant_inherited(self, admin_client, kw, id, unique_database,
unique_table):
@@ -461,29 +479,30 @@ class TestRanger(CustomClusterTestSuite):
[kw, id, "*", "", "", "", "", "", "*", "select", "false"],
[kw, id, "*", "*", "*", "", "", "", "", "select", "false"]])
- # Verify the highest level of resource that contains the specified
resource could
- # be computed when the specified resource is a database
+ # Verify the resources that contain the specified resource could be
computed when
+ # the specified resource is a database.
result = self.client.execute("show grant {0} {1} on database {2}"
.format(kw, id, unique_database))
TestRanger._check_privileges(result, [
[kw, id, "*", "", "", "", "", "", "*", "select", "false"],
[kw, id, "*", "*", "*", "", "", "", "", "select", "false"]])
- # Verify the highest level of resource that contains the specified
resource could
- # be computed when the specified resource is a table
+ # Verify the resource that contains the specified resource could be
computed when
+ # the specified resource is a table.
result = self.client.execute("show grant {0} {1} on table {2}.{3}"
.format(kw, id, unique_database,
unique_table))
TestRanger._check_privileges(result, [
[kw, id, "*", "*", "*", "", "", "", "", "select", "false"]])
- # Verify the highest level of resource that contains the specified
resource could
- # be computed when the specified resource is a column
+ # Verify the resource that contains the specified resource could be
computed when
+ # the specified resource is a column.
result = self.client.execute("show grant {0} {1} on column {2}.{3}.x"
.format(kw, id, unique_database,
unique_table))
TestRanger._check_privileges(result, [
[kw, id, "*", "*", "*", "", "", "", "", "select", "false"]])
- # Grant the create privilege on database and verify
+ # Grant the create privilege on database and verify that the resources
that contain
+ # the specified database could be computed.
admin_client.execute("grant create on database {0} to {1} {2}"
.format(unique_database, kw, id))
result = self.client.execute("show grant {0} {1} on database {2}"
@@ -495,7 +514,8 @@ class TestRanger(CustomClusterTestSuite):
[kw, id, unique_database, "*", "*", "", "", "", "", "create",
"false"]
])
- # Grant the insert privilege on table and verify
+ # Grant the insert privilege on table and verify that the resources that
contain
+ # the specified table could be computed.
admin_client.execute("grant insert on table {0}.{1} to {2} {3}"
.format(unique_database, unique_table, kw, id))
result = self.client.execute("show grant {0} {1} on table {2}.{3}"
@@ -506,7 +526,9 @@ class TestRanger(CustomClusterTestSuite):
[kw, id, unique_database, unique_table, "*", "", "", "", "",
"insert", "false"]
])
- # Grant the select privilege on column and verify
+ # Grant the select privilege on the column x and verify that
+ # the select privilege on the column x masks the select privilege on
every
+ # database, every table, and every column.
admin_client.execute("grant select(x) on table {0}.{1} to {2} {3}"
.format(unique_database, unique_table, kw, id))
result = self.client.execute("show grant {0} {1} on column {2}.{3}.x"
@@ -517,7 +539,10 @@ class TestRanger(CustomClusterTestSuite):
[kw, id, unique_database, unique_table, "x", "", "", "", "",
"select", "false"]
])
- # The insert privilege on table masks the select privilege just added
+ # Grant the select privilege on the table and verify that
+ # the select privilege on the column x masks
+ # a) the select privilege on every database, every table, and every
column,
+ # b) the select privilege on the table just added.
admin_client.execute("grant select on table {0}.{1} to {2} {3}"
.format(unique_database, unique_table, kw, id))
result = self.client.execute("show grant {0} {1} on column {2}.{3}.x"
@@ -528,8 +553,13 @@ class TestRanger(CustomClusterTestSuite):
[kw, id, unique_database, unique_table, "x", "", "", "", "",
"select", "false"]
])
- # The all privilege on table masks the privileges of insert and select,
but not the
- # select privilege on column.
+ # Grant the all privilege on the table and verify that
+ # a) the all privilege on a table masks the insert and select privileges
+ # on the same table,
+ # b) the all privilege on a table masks the create privilege on the
resource that
+ # contains this table (a database in this case),
+ # c) the select privilege on the column x masks the select privilege on
the other
+ # two with resources containing the column x.
admin_client.execute("grant all on table {0}.{1} to {2} {3}"
.format(unique_database, unique_table, kw, id))
result = self.client.execute("show grant {0} {1} on column {2}.{3}.x"
@@ -539,6 +569,19 @@ class TestRanger(CustomClusterTestSuite):
[kw, id, unique_database, unique_table, "x", "", "", "", "",
"select", "false"]
])
+ # Grant the insert privilege on the column x and verify that
+ # the select privilege on the column x does not mask the insert
privilege on the
+ # same column.
+ admin_client.execute("grant insert(x) on table {0}.{1} to {2} {3}"
+ .format(unique_database, unique_table, kw, id))
+ result = self.client.execute("show grant {0} {1} on column {2}.{3}.x"
+ .format(kw, id, unique_database,
unique_table))
+ TestRanger._check_privileges(result, [
+ [kw, id, unique_database, unique_table, "*", "", "", "", "", "all",
"false"],
+ [kw, id, unique_database, unique_table, "x", "", "", "", "",
"insert", "false"],
+ [kw, id, unique_database, unique_table, "x", "", "", "", "",
"select", "false"]
+ ])
+
finally:
admin_client.execute("revoke select on server from {0} {1}".format(kw,
id))
admin_client.execute("revoke create on database {0} from {1} {2}"
@@ -551,6 +594,8 @@ class TestRanger(CustomClusterTestSuite):
.format(unique_database, unique_table, kw, id))
admin_client.execute("revoke all on table {0}.{1} from {2} {3}"
.format(unique_database, unique_table, kw, id))
+ admin_client.execute("revoke insert(x) on table {0}.{1} from {2} {3}"
+ .format(unique_database, unique_table, kw, id))
@staticmethod
def _grant_ranger_privilege(user, resource, access):
@@ -744,7 +789,7 @@ class TestRanger(CustomClusterTestSuite):
return json.loads(r.content)["id"]
@staticmethod
- def _add_deny_policy(policy_name, user, db, table, column):
+ def _add_deny_policy(policy_name, user, db, table, column, access_type):
""" Adds a deny policy and returns the policy id"""
data = {
"name": policy_name,
@@ -773,7 +818,7 @@ class TestRanger(CustomClusterTestSuite):
{
"accesses": [
{
- "type": "select",
+ "type": access_type,
"isAllowed": True
}
],
@@ -783,16 +828,20 @@ class TestRanger(CustomClusterTestSuite):
}
r = requests.post("{0}/service/public/v2/api/policy".format(RANGER_HOST),
auth=RANGER_AUTH, json=data, headers=REST_HEADERS)
- assert 300 > r.status_code >= 200, r.content
+ assert 300 > r.status_code >= 200, bytes_to_str(r.content)
return json.loads(r.content)["id"]
@staticmethod
def _remove_policy(policy_name):
+ """Removes a policy with the given name if it exists."""
r = requests.delete(
"{0}/service/public/v2/api/policy?servicename=test_impala&policyname={1}".format(
RANGER_HOST, policy_name),
auth=RANGER_AUTH, headers=REST_HEADERS)
- assert 300 > r.status_code >= 200, bytes_to_str(r.content)
+ # 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)
@staticmethod
def _toggle_ranger_policy(self, policy_name, enable):
@@ -908,6 +957,31 @@ class TestRanger(CustomClusterTestSuite):
break
return result
+ @staticmethod
+ def _get_ranger_policy_names(policies, db, tbl, col):
+ """Returns the set of Ranger policy names that match the given database
'db',
+ table 'tbl', and column 'col' in the given 'policies'."""
+ result = set()
+ for policy in policies:
+ resources = policy["resources"]
+ policy_name = str(policy["name"])
+ if "database" in resources and db in resources["database"]["values"] \
+ and "table" in resources and tbl in resources["table"]["values"] \
+ and "column" in resources and col in resources["column"]["values"]:
+ result.add(policy_name)
+ return result
+
+ @staticmethod
+ def _remove_policies(db, tbl, col):
+ """Removes all policies that match the given database 'db', table 'tbl',
+ and column 'col'."""
+ policy_names = set()
+ policies = TestRanger._get_ranger_privileges()
+ policy_names = policy_names \
+ .union(TestRanger._get_ranger_policy_names(policies, db, tbl, col))
+ for name in policy_names:
+ TestRanger._remove_policy(name)
+
def _test_grant_revoke_by_owner(self, unique_name):
non_owner_user = NON_OWNER
non_owner_group = NON_OWNER
@@ -1338,7 +1412,7 @@ class TestRanger(CustomClusterTestSuite):
"alter database {0} set owner user {1}".format(test_db, ADMIN),
ADMIN, True)
result = self._run_query_as_user(
"show tables in {0}".format(test_db), test_user, False)
- err = "User '{0}' does not have privileges to access: {1}.*.*". \
+ err = "User '{0}' does not have privileges to access: {1}". \
format(test_user, test_db)
assert err in str(result)
# test_user is still the owner of the table, so select should work fine.
@@ -1441,7 +1515,7 @@ class TestRanger(CustomClusterTestSuite):
# the table 'test_tbl_2', on which 'grantee_user' had been granted the
SELECT
# privilege.
TestRanger._add_deny_policy(unique_name, grantee_user, unique_database,
test_tbl_2,
- "id")
+ "id", "select")
admin_client.execute("refresh authorization")
# The query is not authorized since the SELECT privilege on the column
'id' in the
@@ -1465,6 +1539,187 @@ class TestRanger(CustomClusterTestSuite):
.format(unique_database))
TestRanger._remove_policy(unique_name)
+ def _test_deny_insert_into_column_non_partitioned_table(self, unique_name):
+ grantee_user = "non_owner"
+ unique_database = unique_name + "_db"
+ table_prefix = "tbl"
+ tables = [table_prefix, table_prefix + "_iceberg"]
+ columns = ["id", "bigint_col"]
+ create_queries = [
+ ("create table {0}.{1} ({2} int, {3} bigint)"
+ .format(unique_database, tables[0], columns[0], columns[1]),
+ tables[0]),
+ ("create table {0}.{1} ({2} int, {3} bigint) stored as iceberg"
+ .format(unique_database, tables[1], columns[0], columns[1]),
+ tables[1])
+ ]
+ error_msg_prefix = ("User '{0}' does not have privileges to execute
'INSERT' on:"
+ .format(grantee_user))
+
+ with self.create_impala_client(user=ADMIN) as admin_client, \
+ self.create_impala_client(user=NON_OWNER) as non_owner_client:
+ try:
+ # Set up a temporary database and a table.
+ admin_client.execute("create database {0}".format(unique_database))
+
+ for create_stmt, tbl in create_queries:
+ insert_queries = [
+ ("insert into {0}.{1}({2}) values (1)"
+ .format(unique_database, tbl, columns[0])),
+ ("insert into {0}.{1}({2}) values (1)"
+ .format(unique_database, tbl, columns[1]))]
+
+ try:
+ admin_client.execute(create_stmt)
+
+ # Grant the INSERT privilege on the table to 'grantee_user'.
+ admin_client.execute("grant insert on table {0}.{1} to user {2}"
+ .format(unique_database, tbl, grantee_user))
+ # Add a deny policy on 'columns[0]' to prevent 'grantee_user' from
inserting
+ # data into this column.
+ TestRanger._add_deny_policy(unique_name + "_deny_" + columns[0],
+ grantee_user, unique_database, tbl, columns[0], "update")
+ admin_client.execute("refresh authorization")
+
+ # Verify inserting into 'columns[0]' by 'grantee_user' is denied.
+ result = self.execute_query_expect_failure(non_owner_client,
+ insert_queries[0])
+ assert error_msg_prefix in str(result)
+
+ # For 'insert_queries[1]', since Impala registered the INSERT
privilege on
+ # 'columns[1]' but not on 'columns[0]', inserting into
'columns[1]' by
+ # 'grantee_user' would be allowed.
+ self.execute_query_expect_success(non_owner_client,
insert_queries[1])
+
+ # Add a column masking policy on 'columns[0]' to prevent
'grantee_user' from
+ # inserting data into any column of the table.
+ TestRanger._add_column_masking_policy(unique_name +
"_column_masking_"
+ + columns[0], grantee_user, unique_database, tbl, columns[0],
"CUSTOM",
+ "id * 100")
+ admin_client.execute("refresh authorization")
+
+ # Verify inserting into 'columns[1]' by 'grantee_user' is denied
even
+ # though the column masking policy is defined on 'columns[0]'.
+ result = self.execute_query_expect_failure(non_owner_client,
+ insert_queries[1])
+ assert error_msg_prefix in str(result)
+ finally:
+ TestRanger._remove_policy(unique_name + "_deny_" + columns[0])
+ TestRanger._remove_policy(unique_name + "_column_masking_" +
columns[0])
+ admin_client.execute("revoke insert on table {0}.{1} from user {2}"
+ .format(unique_database, tbl, grantee_user))
+ # This removes the empty policy on the table 'unique_database.tbl'
that is
+ # not associated with any principal. This policy was added due to
the GRANT
+ # statement in the try block. Recall that the REVOKE statement
above
+ # could only remove the associated user but not the entire policy.
We have to
+ # remove the entire policy in order to add a deny policy on the
same table in
+ # the next try block.
+ TestRanger._remove_policies(unique_database, tbl, "*")
+ admin_client.execute("refresh authorization")
+
+ try:
+ TestRanger._add_deny_policy(unique_name + "_deny_" + tbl,
grantee_user,
+ unique_database, tbl, "*", "update")
+ # Grant the INSERT privilege on the column 'columns[1]' to
'grantee_user'.
+ admin_client.execute("grant insert ({0}) on table {1}.{2} to user
{3}"
+ .format(columns[1], unique_database, tbl, grantee_user))
+ admin_client.execute("refresh authorization")
+
+ # Verify inserting into the 'columns[1]' by 'grantee_user' is
denied due to
+ # the deny policy on the table.
+ result = self.execute_query_expect_failure(non_owner_client,
+ insert_queries[1])
+ assert error_msg_prefix in str(result)
+ finally:
+ TestRanger._remove_policy(unique_name + "_deny_" + tbl)
+ admin_client.execute("revoke insert ({0}) on table {1}.{2} from
user {3}"
+ .format(columns[1], unique_database, tbl, grantee_user))
+ finally:
+ admin_client.execute("drop database if exists {0} cascade"
+ .format(unique_database))
+
+ def _test_deny_insert_into_column_partitioned_table(self, unique_name):
+ grantee_user = "non_owner"
+ unique_database = unique_name + "_db"
+ unique_table = unique_name + "_tbl"
+ unique_tables = [unique_table, unique_table + "_iceberg"]
+ non_part_columns = ["id"]
+ part_columns = ["year"]
+ create_queries = [
+ ("create table {0}.{1} ({2} int) partitioned by ({3} int)"
+ .format(unique_database, unique_tables[0], non_part_columns[0],
part_columns[0]),
+ unique_tables[0]),
+ ("create table {0}.{1} ({2} int) partitioned by ({3} int) stored as
iceberg"
+ .format(unique_database, unique_tables[1], non_part_columns[0],
part_columns[0]),
+ unique_tables[1])
+ ]
+ error_msg_prefix = ("User '{0}' does not have privileges to execute
'INSERT' on:"
+ .format(grantee_user))
+
+ with self.create_impala_client(user=ADMIN) as admin_client, \
+ self.create_impala_client(user=NON_OWNER) as non_owner_client:
+ try:
+ # Set up a temporary database and a table.
+ admin_client.execute("drop database if exists {0} cascade"
+ .format(unique_database))
+ admin_client.execute("create database {0}".format(unique_database))
+
+ for create_stmt, tbl in create_queries:
+ insert_query = (
+ "insert into {0}.{1}({2}, {3}) values (1, 2025)"
+ .format(unique_database, tbl, non_part_columns[0],
+ part_columns[0]))
+ try:
+ admin_client.execute(create_stmt)
+
+ # Grant the INSERT privilege on the table to 'grantee_user'.
+ admin_client.execute("grant insert on table {0}.{1} to user {2}"
+ .format(unique_database, tbl, grantee_user))
+ # Verify 'grantee_user' is able to insert data into the table.
+ self.execute_query_expect_success(non_owner_client, insert_query)
+
+ # Add a deny policy on 'part_columns[0]' to prevent 'grantee_user'
from
+ # inserting data into this column.
+ TestRanger._add_deny_policy(unique_name + "_deny_" +
part_columns[0],
+ grantee_user, unique_database, tbl, part_columns[0],
+ "update")
+ admin_client.execute("refresh authorization")
+
+ # Verify inserting into 'part_columns[0]' by 'grantee_user' is
denied.
+ result = self.execute_query_expect_failure(non_owner_client,
insert_query)
+ assert error_msg_prefix in str(result)
+ finally:
+ TestRanger._remove_policy(unique_name + "_deny_" + part_columns[0])
+ admin_client.execute("revoke insert on table {0}.{1} from user {2}"
+ .format(unique_database, tbl, grantee_user))
+ admin_client.execute("refresh authorization")
+
+ try:
+ # Grant the INSERT privilege on the table to 'grantee_user'.
+ admin_client.execute("grant insert on table {0}.{1} to user {2}"
+ .format(unique_database, tbl, grantee_user))
+ # Verify 'grantee_user' is able to insert data into the table.
+ self.execute_query_expect_success(non_owner_client, insert_query)
+
+ # Add a column masking policy on 'part_columns[0]' to prevent
'grantee_user'
+ # from inserting data into this column.
+ TestRanger._add_column_masking_policy(
+ unique_name + "_column_masking_" + part_columns[0], grantee_user,
+ unique_database, tbl, part_columns[0], "CUSTOM", "id * 100")
+ admin_client.execute("refresh authorization")
+
+ # Verify inserting into 'part_columns[0]' by 'grantee_user' is
denied.
+ result = self.execute_query_expect_failure(non_owner_client,
insert_query)
+ assert error_msg_prefix in str(result)
+ finally:
+ TestRanger._remove_policy(unique_name + "_column_masking_" +
part_columns[0])
+ admin_client.execute("revoke insert on table {0}.{1} from user {2}"
+ .format(unique_database, tbl, grantee_user))
+ admin_client.execute("refresh authorization")
+ finally:
+ admin_client.execute("drop database if exists {0} cascade"
+ .format(unique_database))
+
def _test_select_calcite_frontend(self, unique_name):
grantee_user = "non_owner"
with self.create_impala_client(user=ADMIN) as admin_client, \
@@ -2569,7 +2824,7 @@ class TestRangerLocalCatalog(TestRanger):
# Check "show functions" with no privilege granted.
result = self._run_query_as_user("show functions in
{0}".format(unique_database),
user1, False)
- err = "User '{0}' does not have privileges to access: {1}.*.*". \
+ err = "User '{0}' does not have privileges to access: {1}". \
format(user1, unique_database)
assert err in str(result)
for privilege in privileges:
@@ -3495,6 +3750,10 @@ class TestRangerLocalCatalog(TestRanger):
TestRanger._toggle_ranger_policy(self, policy_name, True)
admin_client.execute("refresh authorization")
+ def test_deny_insert_into_column_with_local_catalog(self, unique_name):
+ self._test_deny_insert_into_column_non_partitioned_table(unique_name)
+ self._test_deny_insert_into_column_partitioned_table(unique_name)
+
class TestRangerColumnMaskingTpchNested(CustomClusterTestSuite):
"""