You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by st...@apache.org on 2020/04/04 12:36:13 UTC

[impala] branch master updated: IMPALA-9350: Produce Ranger audits for column masking

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

stigahuang 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 d877dbc  IMPALA-9350: Produce Ranger audits for column masking
d877dbc is described below

commit d877dbc572e95f086b37142e1d41231d5b6b7f9f
Author: Fang-Yu Rao <fa...@cloudera.com>
AuthorDate: Wed Mar 4 17:10:00 2020 -0800

    IMPALA-9350: Produce Ranger audits for column masking
    
    Support for column mask transformation was provided in IMPALA-9009.
    However, the corresponding audit logs were not generated when a query
    involved policies of column masking. This patch fixes the issue by
    providing the Ranger plug-in with a non-null instance of
    RangerBufferAuditHandler when calling evalDataMaskPolicies() so that
    the corresponding audit log could be stored in the audit handler after
    the evaluation of a policy involving column masking.
    
    Testing:
    - Added a FE test to verify the audit logs corresponding to column
      masking are produced.
    - Verified that the corresponding audit logs are indeed produced in a
      3-node cluster with Ranger and Solr installed.
    - Verified that the patch passed the exhaustive tests in the DEBUG build.
    
    Change-Id: I9d8a1181234dcef580f68f56c24ad7e962cfe58e
    Reviewed-on: http://gerrit.cloudera.org:8080/15412
    Reviewed-by: Quanlong Huang <hu...@gmail.com>
    Tested-by: Impala Public Jenkins <im...@cloudera.com>
---
 .../apache/impala/analysis/AnalysisContext.java    |  39 +++++---
 .../java/org/apache/impala/analysis/Analyzer.java  |  17 ++--
 .../impala/analysis/CreateTableAsSelectStmt.java   |   2 +-
 .../org/apache/impala/analysis/FromClause.java     |   1 -
 .../org/apache/impala/analysis/InlineViewRef.java  |   8 +-
 .../impala/authorization/AuthorizationChecker.java |   4 +-
 .../authorization/NoopAuthorizationFactory.java    |   2 +-
 .../org/apache/impala/authorization/TableMask.java |   7 +-
 .../ranger/RangerAuthorizationChecker.java         |  33 +++++--
 .../sentry/SentryAuthorizationChecker.java         |   2 +-
 .../authorization/ranger/RangerAuditLogTest.java   | 110 +++++++++++++++++++++
 .../org/apache/impala/common/FrontendTestBase.java |   2 +-
 12 files changed, 191 insertions(+), 36 deletions(-)

diff --git a/fe/src/main/java/org/apache/impala/analysis/AnalysisContext.java b/fe/src/main/java/org/apache/impala/analysis/AnalysisContext.java
index 836a2da..feddb4c 100644
--- a/fe/src/main/java/org/apache/impala/analysis/AnalysisContext.java
+++ b/fe/src/main/java/org/apache/impala/analysis/AnalysisContext.java
@@ -390,7 +390,12 @@ public class AnalysisContext {
   }
 
   public Analyzer createAnalyzer(StmtTableCache stmtTableCache) {
-    Analyzer result = new Analyzer(stmtTableCache, queryCtx_, authzFactory_);
+    return createAnalyzer(stmtTableCache, null);
+  }
+
+  public Analyzer createAnalyzer(StmtTableCache stmtTableCache,
+      AuthorizationContext authzCtx) {
+    Analyzer result = new Analyzer(stmtTableCache, queryCtx_, authzFactory_, authzCtx);
     result.setUseHiveColLabels(useHiveColLabels_);
     return result;
   }
@@ -411,8 +416,25 @@ public class AnalysisContext {
 
     // Analyze statement and record exception.
     AnalysisException analysisException = null;
+    TClientRequest clientRequest;
+    AuthorizationContext authzCtx = null;
+
     try {
-      analyze(stmtTableCache);
+      clientRequest = queryCtx_.getClient_request();
+      authzCtx = authzChecker.createAuthorizationContext(true,
+          clientRequest.isSetRedacted_stmt() ?
+              clientRequest.getRedacted_stmt() : clientRequest.getStmt(),
+          queryCtx_.getSession(), Optional.of(timeline_));
+      // TODO (IMPALA-9597): Generating column masking audit events in the analysis phase
+      // suffers from the drawback of losing the opportunity to control the final
+      // results. Redundant audits could be generated. For example, we will always
+      // generate audit events for column masking even though the requesting user is not
+      // granted the necessary privilege on the specified resource because
+      // AuthorizationChecker#postAuthorize() is always called whether there is an
+      // AuthorizationException or not. Another example is that if a table occurs several
+      // times in a query, we would have duplicate audits for the same column involved in
+      // a column masking policy.
+      analyze(stmtTableCache, authzCtx);
     } catch (AnalysisException e) {
       analysisException = e;
     }
@@ -421,13 +443,7 @@ public class AnalysisContext {
     // Authorize statement and record exception. Authorization relies on information
     // collected during analysis.
     AuthorizationException authException = null;
-    AuthorizationContext authzCtx = null;
     try {
-      TClientRequest clientRequest = queryCtx_.getClient_request();
-      authzCtx = authzChecker.createAuthorizationContext(true,
-          clientRequest.isSetRedacted_stmt() ?
-              clientRequest.getRedacted_stmt() : clientRequest.getStmt(),
-          queryCtx_.getSession(), Optional.of(timeline_));
       authzChecker.authorize(authzCtx, analysisResult_, catalog_);
     } catch (AuthorizationException e) {
       authException = e;
@@ -449,10 +465,11 @@ public class AnalysisContext {
    * given loaded tables. Performs expr and subquery rewrites which require re-analyzing
    * the transformed statement.
    */
-  private void analyze(StmtTableCache stmtTableCache) throws AnalysisException {
+  private void analyze(StmtTableCache stmtTableCache, AuthorizationContext authzCtx)
+      throws AnalysisException {
     Preconditions.checkNotNull(analysisResult_);
     Preconditions.checkNotNull(analysisResult_.stmt_);
-    analysisResult_.analyzer_ = createAnalyzer(stmtTableCache);
+    analysisResult_.analyzer_ = createAnalyzer(stmtTableCache, authzCtx);
     analysisResult_.stmt_.analyze(analysisResult_.analyzer_);
     // Enforce the statement expression limit at the end of analysis so that there is an
     // accurate count of the total number of expressions. The first analyze() call is not
@@ -498,7 +515,7 @@ public class AnalysisContext {
         analysisResult_.analyzer_.getPrivilegeReqs();
 
     // Re-analyze the stmt with a new analyzer.
-    analysisResult_.analyzer_ = createAnalyzer(stmtTableCache);
+    analysisResult_.analyzer_ = createAnalyzer(stmtTableCache, authzCtx);
     // We restore the privileges collected in the first pass below. So, no point in
     // collecting them again.
     analysisResult_.analyzer_.setEnablePrivChecks(false);
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 f79e109..a0772d7 100644
--- a/fe/src/main/java/org/apache/impala/analysis/Analyzer.java
+++ b/fe/src/main/java/org/apache/impala/analysis/Analyzer.java
@@ -36,6 +36,7 @@ import org.apache.impala.analysis.Path.PathType;
 import org.apache.impala.analysis.StmtMetadataLoader.StmtTableCache;
 import org.apache.impala.authorization.AuthorizationChecker;
 import org.apache.impala.authorization.AuthorizationConfig;
+import org.apache.impala.authorization.AuthorizationContext;
 import org.apache.impala.authorization.AuthorizationFactory;
 import org.apache.impala.authorization.Privilege;
 import org.apache.impala.authorization.PrivilegeRequest;
@@ -323,6 +324,7 @@ public class Analyzer {
   private static class GlobalState {
     public final TQueryCtx queryCtx;
     public final AuthorizationFactory authzFactory;
+    public final AuthorizationContext authzCtx;
     public final DescriptorTable descTbl = new DescriptorTable();
     public final IdGenerator<ExprId> conjunctIdGenerator = ExprId.createGenerator();
     public final ColumnLineageGraph lineageGraph;
@@ -450,9 +452,10 @@ public class Analyzer {
     private int numStmtExprs_ = 0;
 
     public GlobalState(StmtTableCache stmtTableCache, TQueryCtx queryCtx,
-        AuthorizationFactory authzFactory) {
+        AuthorizationFactory authzFactory, AuthorizationContext authzCtx) {
       this.stmtTableCache = stmtTableCache;
       this.queryCtx = queryCtx;
+      this.authzCtx = authzCtx;
       this.authzFactory = authzFactory;
       this.lineageGraph = new ColumnLineageGraph();
       List<ExprRewriteRule> rules = new ArrayList<>();
@@ -469,7 +472,7 @@ public class Analyzer {
         rules.add(ExtractCommonConjunctRule.INSTANCE);
         if (queryCtx.getClient_request().getQuery_options().isEnable_cnf_rewrites()) {
           rules.add(new ConvertToCNFRule(queryCtx.getClient_request().getQuery_options()
-                  .getMax_cnf_exprs(),true));
+              .getMax_cnf_exprs(),true));
         }
         // Relies on FoldConstantsRule and NormalizeExprsRule.
         rules.add(SimplifyConditionalsRule.INSTANCE);
@@ -522,9 +525,9 @@ public class Analyzer {
   private boolean hasEmptySpjResultSet_ = false;
 
   public Analyzer(StmtTableCache stmtTableCache, TQueryCtx queryCtx,
-      AuthorizationFactory authzFactory) {
+      AuthorizationFactory authzFactory, AuthorizationContext authzCtx) {
     ancestors_ = new ArrayList<>();
-    globalState_ = new GlobalState(stmtTableCache, queryCtx, authzFactory);
+    globalState_ = new GlobalState(stmtTableCache, queryCtx, authzFactory, authzCtx);
     user_ = new User(TSessionStateUtil.getEffectiveUser(queryCtx.session));
   }
 
@@ -557,7 +560,8 @@ public class Analyzer {
    */
   public static Analyzer createWithNewGlobalState(Analyzer parentAnalyzer) {
     GlobalState globalState = new GlobalState(parentAnalyzer.globalState_.stmtTableCache,
-        parentAnalyzer.getQueryCtx(), parentAnalyzer.getAuthzFactory());
+        parentAnalyzer.getQueryCtx(), parentAnalyzer.getAuthzFactory(),
+        parentAnalyzer.getAuthzCtx());
     return new Analyzer(parentAnalyzer, globalState);
   }
 
@@ -776,7 +780,7 @@ public class Analyzer {
       try {
         if (!tableMask.needsMaskingOrFiltering()) return resolvedTableRef;
         return InlineViewRef.createTableMaskView(resolvedPath, resolvedTableRef,
-            tableMask);
+            tableMask, getAuthzCtx());
       } catch (InternalException e) {
         LOG.error("Error performing table masking", e);
         throw new AnalysisException("Error performing table masking", e);
@@ -2710,6 +2714,7 @@ public class Analyzer {
   public AuthorizationConfig getAuthzConfig() {
     return getAuthzFactory().getAuthorizationConfig();
   }
+  public AuthorizationContext getAuthzCtx() { return globalState_.authzCtx; }
   public boolean isAuthzEnabled() { return getAuthzConfig().isEnabled(); }
   public ListMap<TNetworkAddress> getHostIndex() { return globalState_.hostIndex; }
   public ColumnLineageGraph getColumnLineageGraph() { return globalState_.lineageGraph; }
diff --git a/fe/src/main/java/org/apache/impala/analysis/CreateTableAsSelectStmt.java b/fe/src/main/java/org/apache/impala/analysis/CreateTableAsSelectStmt.java
index 2c4e442..643caa8 100644
--- a/fe/src/main/java/org/apache/impala/analysis/CreateTableAsSelectStmt.java
+++ b/fe/src/main/java/org/apache/impala/analysis/CreateTableAsSelectStmt.java
@@ -141,7 +141,7 @@ public class CreateTableAsSelectStmt extends StatementBase {
     // over the full INSERT statement. To avoid duplicate registrations of table/colRefs,
     // create a new root analyzer and clone the query statement for this initial pass.
     Analyzer dummyRootAnalyzer = new Analyzer(analyzer.getStmtTableCache(),
-        analyzer.getQueryCtx(), analyzer.getAuthzFactory());
+        analyzer.getQueryCtx(), analyzer.getAuthzFactory(), analyzer.getAuthzCtx());
     QueryStmt tmpQueryStmt = insertStmt_.getQueryStmt().clone();
     Analyzer tmpAnalyzer = new Analyzer(dummyRootAnalyzer);
     tmpAnalyzer.setUseHiveColLabels(true);
diff --git a/fe/src/main/java/org/apache/impala/analysis/FromClause.java b/fe/src/main/java/org/apache/impala/analysis/FromClause.java
index 159f45c..f81e6be 100644
--- a/fe/src/main/java/org/apache/impala/analysis/FromClause.java
+++ b/fe/src/main/java/org/apache/impala/analysis/FromClause.java
@@ -72,7 +72,6 @@ public class FromClause extends StmtNode implements Iterable<TableRef> {
     TableRef leftTblRef = null;  // the one to the left of tblRef
     for (int i = 0; i < tableRefs_.size(); ++i) {
       TableRef tblRef = tableRefs_.get(i);
-      // Replace non-InlineViewRef table refs with a BaseTableRef or ViewRef.
       tblRef = analyzer.resolveTableRef(tblRef, doTableMasking_);
       tableRefs_.set(i, Preconditions.checkNotNull(tblRef));
       tblRef.setLeftTblRef(leftTblRef);
diff --git a/fe/src/main/java/org/apache/impala/analysis/InlineViewRef.java b/fe/src/main/java/org/apache/impala/analysis/InlineViewRef.java
index 41106f0..f63b689 100644
--- a/fe/src/main/java/org/apache/impala/analysis/InlineViewRef.java
+++ b/fe/src/main/java/org/apache/impala/analysis/InlineViewRef.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
+import org.apache.impala.authorization.AuthorizationContext;
 import org.apache.impala.authorization.TableMask;
 import org.apache.impala.catalog.Column;
 import org.apache.impala.catalog.ColumnStats;
@@ -144,12 +145,15 @@ public class InlineViewRef extends TableRef {
    * @param resolvedPath resolved path for the original table/view
    * @param tableRef original resolved table/view
    * @param tableMask TableMask providing column masking and row filtering policies
+   * @param authzCtx AuthorizationContext containing RangerBufferAuditHandler
    */
   static InlineViewRef createTableMaskView(Path resolvedPath, TableRef tableRef,
-      TableMask tableMask) throws AnalysisException, InternalException {
+      TableMask tableMask, AuthorizationContext authzCtx) throws AnalysisException,
+      InternalException {
     Preconditions.checkNotNull(resolvedPath);
     Preconditions.checkNotNull(resolvedPath.getRootTable());
     Preconditions.checkNotNull(tableRef);
+    Preconditions.checkNotNull(authzCtx);
     Preconditions.checkState(tableRef instanceof InlineViewRef
         || tableRef instanceof BaseTableRef);
     List<Column> columns = resolvedPath.getRootTable().getColumnsInHiveOrder();
@@ -159,7 +163,7 @@ public class InlineViewRef extends TableRef {
       // TODO: only add materialized columns to avoid introducing new privilege
       //  requirements (IMPALA-9223)
       items.add(new SelectListItem(
-          tableMask.createColumnMask(col.getName(), col.getType()),
+          tableMask.createColumnMask(col.getName(), col.getType(), authzCtx),
           /*alias*/ col.getName()));
     }
     SelectList selectList = new SelectList(items);
diff --git a/fe/src/main/java/org/apache/impala/authorization/AuthorizationChecker.java b/fe/src/main/java/org/apache/impala/authorization/AuthorizationChecker.java
index bb1c587..c995164 100644
--- a/fe/src/main/java/org/apache/impala/authorization/AuthorizationChecker.java
+++ b/fe/src/main/java/org/apache/impala/authorization/AuthorizationChecker.java
@@ -94,6 +94,6 @@ public interface AuthorizationChecker {
   /**
    * Returns the column mask string for the given column.
    */
-  String createColumnMask(User user, String dbName, String tableName, String columnName)
-      throws InternalException;
+  String createColumnMask(User user, String dbName, String tableName, String columnName,
+      AuthorizationContext authzCtx) throws InternalException;
 }
diff --git a/fe/src/main/java/org/apache/impala/authorization/NoopAuthorizationFactory.java b/fe/src/main/java/org/apache/impala/authorization/NoopAuthorizationFactory.java
index f57e943..f476b2e 100644
--- a/fe/src/main/java/org/apache/impala/authorization/NoopAuthorizationFactory.java
+++ b/fe/src/main/java/org/apache/impala/authorization/NoopAuthorizationFactory.java
@@ -221,7 +221,7 @@ public class NoopAuthorizationFactory implements AuthorizationFactory {
 
       @Override
       public String createColumnMask(User user, String dbName, String tableName,
-          String columnName) {
+          String columnName, AuthorizationContext authzCtx) {
         return null;
       }
     };
diff --git a/fe/src/main/java/org/apache/impala/authorization/TableMask.java b/fe/src/main/java/org/apache/impala/authorization/TableMask.java
index b210568..732c3ed 100644
--- a/fe/src/main/java/org/apache/impala/authorization/TableMask.java
+++ b/fe/src/main/java/org/apache/impala/authorization/TableMask.java
@@ -66,11 +66,12 @@ public class TableMask {
   /**
    * Return the masked Expr of the given column
    */
-  public Expr createColumnMask(String colName, Type colType)
-      throws InternalException, AnalysisException {
+  public Expr createColumnMask(String colName, Type colType,
+      AuthorizationContext authzCtx) throws InternalException,
+      AnalysisException {
     Preconditions.checkState(!colType.isComplexType());
     String maskedValue = authChecker_.createColumnMask(user_, dbName_, tableName_,
-        colName);
+        colName, authzCtx);
     if (LOG.isTraceEnabled()) {
       LOG.trace("Performing column masking on table {}.{}: {} => {}",
           dbName_, tableName_, colName, maskedValue);
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 2a848aa..8dabebd 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
@@ -276,7 +276,7 @@ public class RangerAuthorizationChecker extends BaseAuthorizationChecker {
    */
   private void authorizeColumnMask(User user, String dbName, String tableName,
       String columnName) throws InternalException, AuthorizationException {
-    if (evalColumnMask(user, dbName, tableName, columnName).isMaskEnabled()) {
+    if (evalColumnMask(user, dbName, tableName, columnName, null).isMaskEnabled()) {
       throw new AuthorizationException(String.format(
           "Column masking is disabled by --enable_column_masking flag. Can't access " +
               "column %s.%s.%s that has column masking policy.",
@@ -288,7 +288,8 @@ public class RangerAuthorizationChecker extends BaseAuthorizationChecker {
   public boolean needsMaskingOrFiltering(User user, String dbName, String tableName,
       List<String> requiredColumns) throws InternalException {
     for (String column: requiredColumns) {
-      if (evalColumnMask(user, dbName, tableName, column).isMaskEnabled()) {
+      if (evalColumnMask(user, dbName, tableName, column, null)
+          .isMaskEnabled()) {
         return true;
       }
     }
@@ -297,14 +298,26 @@ public class RangerAuthorizationChecker extends BaseAuthorizationChecker {
 
   @Override
   public String createColumnMask(User user, String dbName, String tableName,
-      String columnName) throws InternalException {
-    RangerAccessResult accessResult = evalColumnMask(user, dbName, tableName,
-        columnName);
+      String columnName, AuthorizationContext rangerCtx) throws InternalException {
+    RangerBufferAuditHandler auditHandler =
+        ((RangerAuthorizationContext) rangerCtx).getAuditHandler();
+    RangerAccessResult accessResult = evalColumnMask(user, dbName, tableName, columnName,
+        auditHandler);
+
     // No column masking policies, return the original column.
     if (!accessResult.isMaskEnabled()) return columnName;
     String maskType = accessResult.getMaskType();
     RangerServiceDef.RangerDataMaskTypeDef maskTypeDef = accessResult.getMaskTypeDef();
     Preconditions.checkNotNull(maskType);
+    List<AuthzAuditEvent> auditEvents = auditHandler.getAuthzEvents();
+    Preconditions.checkState(!auditEvents.isEmpty());
+    // Recall that the same instance of RangerAuthorizationContext is passed to
+    // createColumnMask() every time this method is called. Thus the same instance of
+    // RangerBufferAuditHandler is provided for evalColumnMask() to log the event.
+    // Since there will be exactly one event added to 'auditEvents' when there is a
+    // corresponding column masking policy, i.e., accessResult.isMaskEnabled() evaluates
+    // to true, we only need to process the last event on 'auditEvents'.
+    auditEvents.get(auditEvents.size() - 1).setAccessType(maskType.toLowerCase());
     // The expression used to replace the original column.
     String maskedColumn = columnName;
     // The expression of the mask type. Column names are referenced by "{col}".
@@ -341,7 +354,8 @@ public class RangerAuthorizationChecker extends BaseAuthorizationChecker {
    * A RangerAccessResult contains the matched policy details and the masked column.
    */
   private RangerAccessResult evalColumnMask(User user, String dbName,
-      String tableName, String columnName) throws InternalException {
+      String tableName, String columnName, RangerBufferAuditHandler auditHandler)
+      throws InternalException {
     RangerAccessResourceImpl resource = new RangerImpalaResourceBuilder()
         .database(dbName)
         .table(tableName)
@@ -349,7 +363,12 @@ public class RangerAuthorizationChecker extends BaseAuthorizationChecker {
         .build();
     RangerAccessRequest req = new RangerAccessRequestImpl(resource,
         SELECT_ACCESS_TYPE, user.getShortName(), getUserGroups(user));
-    return plugin_.evalDataMaskPolicies(req, null);
+    // The method evalDataMaskPolicies() only checks whether there is a corresponding
+    // column masking policy on the Ranger server and thus does not check whether the
+    // requesting user/group is granted the necessary privilege on the specified
+    // resource. No AnalysisException or AuthorizationException will be thrown if the
+    // requesting user/group does not possess the necessary privilege.
+    return plugin_.evalDataMaskPolicies(req, auditHandler);
   }
 
   /**
diff --git a/fe/src/main/java/org/apache/impala/authorization/sentry/SentryAuthorizationChecker.java b/fe/src/main/java/org/apache/impala/authorization/sentry/SentryAuthorizationChecker.java
index 3f1b0e8..1fc0eb9 100644
--- a/fe/src/main/java/org/apache/impala/authorization/sentry/SentryAuthorizationChecker.java
+++ b/fe/src/main/java/org/apache/impala/authorization/sentry/SentryAuthorizationChecker.java
@@ -97,7 +97,7 @@ public class SentryAuthorizationChecker extends BaseAuthorizationChecker {
 
   @Override
   public String createColumnMask(User user, String dbName, String tableName,
-      String columnName) {
+      String columnName, AuthorizationContext authzCtx) {
     return columnName;
   }
 
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 463ee0b..531cc77 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
@@ -32,6 +32,7 @@ import org.apache.ranger.audit.model.AuthzAuditEvent;
 import org.junit.Ignore;
 import org.junit.Test;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
@@ -201,6 +202,115 @@ public class RangerAuditLogTest extends AuthorizationTestBase {
     }, "show partitions functional.alltypes");
   }
 
+  @Test
+  public void testAuditsForColumnMasking() throws ImpalaException {
+    String databaseName = "functional";
+    String tableName = "alltypestiny";
+    String policyNames[] = {"col_mask_custom", "col_mask_null"};
+    String columnNames[] = {"string_col", "date_string_col"};
+    String masks[] = {
+        "  {\n" +
+        "    \"dataMaskType\": \"CUSTOM\",\n" +
+        "    \"valueExpr\": \"concat({col}, 'xyz')\"\n" +
+        "  }\n",
+        "  {\n" +
+        "    \"dataMaskType\": \"MASK_NULL\"\n" +
+        "  }\n"
+    };
+
+    List<String> policies = new ArrayList<>();
+    for (int i = 0; i < masks.length; ++i) {
+      String json = String.format("{\n" +
+          "  \"name\": \"%s\",\n" +
+          "  \"policyType\": 1,\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" +
+          "  \"dataMaskPolicyItems\": [\n" +
+          "    {\n" +
+          "      \"accesses\": [\n" +
+          "        {\n" +
+          "          \"type\": \"select\",\n" +
+          "          \"isAllowed\": true\n" +
+          "        }\n" +
+          "      ],\n" +
+          "      \"users\": [\"%s\"],\n" +
+          "      \"dataMaskInfo\":\n" +
+              "%s" +
+          "    }\n" +
+          "  ]\n" +
+          "}", policyNames[i], RANGER_SERVICE_TYPE, RANGER_SERVICE_NAME, databaseName,
+          tableName, columnNames[i], user_.getShortName(), masks[i]);
+      policies.add(json);
+    }
+
+    try {
+      for (int i = 0; i < masks.length; ++i) {
+        String policyName = policyNames[i];
+        String json = policies.get(i);
+        createRangerPolicy(policyName, json);
+      }
+
+      authzOk(events -> {
+        assertEquals(15, events.size());
+        assertEquals("select * from functional.alltypestiny",
+            events.get(0).getRequestData());
+        assertEventEquals("@column", "mask_null",
+            "functional/alltypestiny/date_string_col", 1, events.get(0));
+        assertEventEquals("@column", "custom", "functional/alltypestiny/string_col", 1,
+            events.get(1));
+        assertEventEquals("@table", "select", "functional/alltypestiny", 1,
+            events.get(2));
+        assertEventEquals("@column", "select", "functional/alltypestiny/id", 1,
+            events.get(3));
+        assertEventEquals("@column", "select", "functional/alltypestiny/bool_col", 1,
+            events.get(4));
+        assertEventEquals("@column", "select", "functional/alltypestiny/tinyint_col", 1,
+            events.get(5));
+        assertEventEquals("@column", "select", "functional/alltypestiny/smallint_col", 1,
+            events.get(6));
+        assertEventEquals("@column", "select", "functional/alltypestiny/int_col", 1,
+            events.get(7));
+        assertEventEquals("@column", "select", "functional/alltypestiny/bigint_col", 1,
+            events.get(8));
+        assertEventEquals("@column", "select", "functional/alltypestiny/float_col", 1,
+            events.get(9));
+        assertEventEquals("@column", "select", "functional/alltypestiny/double_col", 1,
+            events.get(10));
+        assertEventEquals("@column", "select", "functional/alltypestiny/string_col", 1,
+            events.get(11));
+        assertEventEquals("@column", "select", "functional/alltypestiny/timestamp_col", 1,
+            events.get(12));
+        assertEventEquals("@column", "select", "functional/alltypestiny/year", 1,
+            events.get(13));
+        assertEventEquals("@column", "select", "functional/alltypestiny/month", 1,
+            events.get(14));
+      }, "select * from functional.alltypestiny", onTable("functional", "alltypestiny",
+          TPrivilegeLevel.SELECT));
+    } finally {
+      for (int i = 0; i < masks.length; ++i) {
+        String policyName = policyNames[i];
+        deleteRangerPolicy(policyName);
+      }
+    }
+  }
+
   private void authzOk(Consumer<List<AuthzAuditEvent>> resultChecker, String stmt,
       TPrivilege[]... privileges) throws ImpalaException {
     authorize(stmt).ok(privileges);
diff --git a/fe/src/test/java/org/apache/impala/common/FrontendTestBase.java b/fe/src/test/java/org/apache/impala/common/FrontendTestBase.java
index a4f43c4..1cc1213 100644
--- a/fe/src/test/java/org/apache/impala/common/FrontendTestBase.java
+++ b/fe/src/test/java/org/apache/impala/common/FrontendTestBase.java
@@ -392,7 +392,7 @@ public class FrontendTestBase extends AbstractFrontendTest {
 
           @Override
           public String createColumnMask(User user, String dbName, String tableName,
-              String columnName) {
+              String columnName, AuthorizationContext authzCtx) {
             return null;
           }