Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enhancement] Support column access privilege for Ranger #47702

Merged
merged 1 commit into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions conf/ranger/ranger-servicedef-starrocks.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@
"label": "StarRocks View",
"description": "StarRocks View",
"accessTypeRestrictions": [
"select",
Copy link
Contributor

Choose a reason for hiding this comment

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

table needs to delete select

"drop",
"alter"
]
Expand All @@ -168,7 +167,6 @@
"label": "StarRocks Materialized View",
"description": "StarRocks Materialized View",
"accessTypeRestrictions": [
"select",
"refresh",
"drop",
"alter"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ default void checkAnyActionOnAnyTable(UserIdentity currentUser, Set<Long> roleId
}

default void checkColumnsAction(UserIdentity currentUser, Set<Long> roleIds, TableName tableName,
Set<String> columns, PrivilegeType privilegeType) throws AccessDeniedException {
String column, PrivilegeType privilegeType) throws AccessDeniedException {
throw new AccessDeniedException();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
// Copyright 2021-present StarRocks, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.starrocks.privilege;

import com.google.common.collect.Maps;
import com.starrocks.analysis.ParseNode;
import com.starrocks.analysis.TableName;
import com.starrocks.catalog.Column;
import com.starrocks.catalog.InternalCatalog;
import com.starrocks.catalog.Table;
import com.starrocks.catalog.View;
import com.starrocks.qe.ConnectContext;
import com.starrocks.sql.StatementPlanner;
import com.starrocks.sql.analyzer.Authorizer;
import com.starrocks.sql.ast.AstTraverser;
import com.starrocks.sql.ast.DeleteStmt;
import com.starrocks.sql.ast.InsertStmt;
import com.starrocks.sql.ast.QueryStatement;
import com.starrocks.sql.ast.TableRelation;
import com.starrocks.sql.ast.UpdateStmt;
import com.starrocks.sql.ast.ViewRelation;
import com.starrocks.sql.optimizer.OptExpression;
import com.starrocks.sql.optimizer.OptExpressionVisitor;
import com.starrocks.sql.optimizer.Optimizer;
import com.starrocks.sql.optimizer.OptimizerConfig;
import com.starrocks.sql.optimizer.base.ColumnRefFactory;
import com.starrocks.sql.optimizer.base.ColumnRefSet;
import com.starrocks.sql.optimizer.base.PhysicalPropertySet;
import com.starrocks.sql.optimizer.operator.Operator;
import com.starrocks.sql.optimizer.operator.logical.LogicalScanOperator;
import com.starrocks.sql.optimizer.operator.scalar.ColumnRefOperator;
import com.starrocks.sql.optimizer.rule.RuleSetType;
import com.starrocks.sql.optimizer.transformer.LogicalPlan;
import com.starrocks.sql.optimizer.transformer.RelationTransformer;
import com.starrocks.sql.optimizer.transformer.TransformerContext;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class ColumnPrivilege {
public static void check(ConnectContext context, QueryStatement stmt) {
Map<TableName, Table> tableNameTableObj = Maps.newHashMap();
Map<Table, TableName> tableObjectToTableName = Maps.newHashMap();
new TableNameCollector(tableNameTableObj, tableObjectToTableName).visit(stmt);

Set<TableName> tableUsedExternalAccessController = new HashSet<>();
for (TableName tableName : tableNameTableObj.keySet()) {
String catalog = tableName.getCatalog() == null ?
InternalCatalog.DEFAULT_INTERNAL_CATALOG_NAME : tableName.getCatalog();
if (Authorizer.getInstance().getAccessControlOrDefault(catalog) instanceof ExternalAccessController) {
tableUsedExternalAccessController.add(tableName);
}
}

Map<TableName, Set<String>> scanColumns = new HashMap<>();
OptExpression optimizedPlan;
if (!tableUsedExternalAccessController.isEmpty()) {
/*
* The column access privilege of the query need to use the pruned column list.
* Because the unused columns will not check the column access privilege.
* For example, the table contains two columns v1 and v2, and user u1 only has
* the access privilege to v1, the select v1 from (select * from tbl) t can be checked because v2 has been pruned.
*/
ColumnRefFactory columnRefFactory = new ColumnRefFactory();
LogicalPlan logicalPlan;
Map<Operator, ParseNode> optToAstMap = StatementPlanner.makeOptToAstMap(context.getSessionVariable());

TransformerContext transformerContext = new TransformerContext(columnRefFactory, context, optToAstMap);
logicalPlan = new RelationTransformer(transformerContext).transformWithSelectLimit(stmt.getQueryRelation());

OptimizerConfig optimizerConfig = new OptimizerConfig(OptimizerConfig.OptimizerAlgorithm.RULE_BASED);
optimizerConfig.disableRuleSet(RuleSetType.SINGLE_TABLE_MV_REWRITE);
optimizerConfig.disableRuleSet(RuleSetType.MULTI_TABLE_MV_REWRITE);
optimizerConfig.disableRuleSet(RuleSetType.PRUNE_EMPTY_OPERATOR);
Optimizer optimizer = new Optimizer(optimizerConfig);
optimizedPlan = optimizer.optimize(context, logicalPlan.getRoot(),
new PhysicalPropertySet(), new ColumnRefSet(logicalPlan.getOutputColumn()), columnRefFactory);

optimizedPlan.getOp().accept(new ScanColumnCollector(tableObjectToTableName, scanColumns), optimizedPlan, null);
}

for (TableName tableName : tableNameTableObj.keySet()) {
if (tableUsedExternalAccessController.contains(tableName)) {
Set<String> columns = scanColumns.getOrDefault(tableName, new HashSet<>());
for (String column : columns) {
try {
Authorizer.checkColumnsAction(context.getCurrentUserIdentity(), context.getCurrentRoleIds(),
tableName, column, PrivilegeType.SELECT);
} catch (AccessDeniedException e) {
AccessDeniedException.reportAccessDenied(
tableName.getCatalog(),
context.getCurrentUserIdentity(), context.getCurrentRoleIds(),
PrivilegeType.SELECT.name(), ObjectType.COLUMN.name(), column);
}
}
} else {
Table table = tableNameTableObj.get(tableName);

if (table instanceof View) {
try {
// for privilege checking, treat connector view as table
if (table.isConnectorView()) {
Authorizer.checkTableAction(context.getCurrentUserIdentity(), context.getCurrentRoleIds(),
tableName, PrivilegeType.SELECT);
} else {
Authorizer.checkViewAction(context.getCurrentUserIdentity(), context.getCurrentRoleIds(),
tableName, PrivilegeType.SELECT);
}
} catch (AccessDeniedException e) {
AccessDeniedException.reportAccessDenied(
InternalCatalog.DEFAULT_INTERNAL_CATALOG_NAME,
context.getCurrentUserIdentity(), context.getCurrentRoleIds(),
PrivilegeType.SELECT.name(), ObjectType.VIEW.name(), tableName.getTbl());
}
} else if (table.isMaterializedView()) {
try {
Authorizer.checkMaterializedViewAction(context.getCurrentUserIdentity(), context.getCurrentRoleIds(),
tableName, PrivilegeType.SELECT);
} catch (AccessDeniedException e) {
AccessDeniedException.reportAccessDenied(
InternalCatalog.DEFAULT_INTERNAL_CATALOG_NAME,
context.getCurrentUserIdentity(), context.getCurrentRoleIds(),
PrivilegeType.SELECT.name(), ObjectType.MATERIALIZED_VIEW.name(), tableName.getTbl());
}
} else {
try {
Authorizer.checkTableAction(context.getCurrentUserIdentity(), context.getCurrentRoleIds(),
tableName.getCatalog(), tableName.getDb(), table.getName(), PrivilegeType.SELECT);
} catch (AccessDeniedException e) {
AccessDeniedException.reportAccessDenied(
tableName.getCatalog(),
context.getCurrentUserIdentity(), context.getCurrentRoleIds(),
PrivilegeType.SELECT.name(), ObjectType.TABLE.name(), tableName.getTbl());
}
}
}
}
}

public static class ScanColumnCollector extends OptExpressionVisitor<Void, Void> {
private final Map<TableName, Set<String>> scanColumns;
private final Map<Table, TableName> tableObjToTableName;

public ScanColumnCollector(
Map<Table, TableName> tableObjToTableName,
Map<TableName, Set<String>> scanColumns) {
this.scanColumns = scanColumns;
this.tableObjToTableName = tableObjToTableName;
}

@Override
public Void visit(OptExpression optExpression, Void context) {
for (OptExpression input : optExpression.getInputs()) {
input.getOp().accept(this, input, null);
}
return null;
}

@Override
public Void visitLogicalTableScan(OptExpression node, Void context) {
LogicalScanOperator operator = (LogicalScanOperator) node.getOp();
Table table = operator.getTable();
TableName tableName = tableObjToTableName.get(table);

if (!scanColumns.containsKey(tableName)) {
scanColumns.put(tableName, new HashSet<>());
}

Set<String> tableColumns = scanColumns.get(tableName);
for (Map.Entry<ColumnRefOperator, Column> c : operator.getColRefToColumnMetaMap().entrySet()) {
String columName = c.getValue().getName();
tableColumns.add(columName);
}
return null;
}
}

private static class TableNameCollector extends AstTraverser<Void, Void> {
private final Map<TableName, Table> tableNameToTableObj;
private final Map<Table, TableName> tableTableNameMap;

public TableNameCollector(Map<TableName, Table> tableNameToTableObj, Map<Table, TableName> tableTableNameMap) {
this.tableNameToTableObj = tableNameToTableObj;
this.tableTableNameMap = tableTableNameMap;
}

@Override
public Void visitQueryStatement(QueryStatement statement, Void context) {
return visit(statement.getQueryRelation());
}

@Override
public Void visitInsertStatement(InsertStmt node, Void context) {
Table table = node.getTargetTable();
tableNameToTableObj.put(node.getTableName(), table);
tableTableNameMap.put(table, node.getTableName());
return super.visitInsertStatement(node, context);
}

@Override
public Void visitUpdateStatement(UpdateStmt node, Void context) {
Table table = node.getTable();
tableNameToTableObj.put(node.getTableName(), table);
tableTableNameMap.put(table, node.getTableName());
return super.visitUpdateStatement(node, context);
}

@Override
public Void visitDeleteStatement(DeleteStmt node, Void context) {
Table table = node.getTable();
tableNameToTableObj.put(node.getTableName(), table);
tableTableNameMap.put(table, node.getTableName());
return super.visitDeleteStatement(node, context);
}

@Override
public Void visitTable(TableRelation node, Void context) {
Table table = node.getTable();
tableNameToTableObj.put(node.getName(), table);
tableTableNameMap.put(table, node.getName());
return null;
}

@Override
public Void visitView(ViewRelation node, Void context) {
Table table = node.getView();
tableNameToTableObj.put(node.getName(), table);
tableTableNameMap.put(table, node.getName());
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,6 @@ public void checkAnyActionOnAnyTable(UserIdentity currentUser, Set<Long> roleIds
checkAnyActionOnTable(currentUser, roleIds, new TableName(catalog, db, "*"));
}

@Override
public void checkColumnsAction(UserIdentity currentUser, Set<Long> roleIds, TableName tableName,
Set<String> columns, PrivilegeType privilegeType) throws AccessDeniedException {
throw new AccessDeniedException("Column-level access control not implemented");
}

@Override
public void checkViewAction(UserIdentity currentUser, Set<Long> roleIds, TableName tableName, PrivilegeType privilegeType)
throws AccessDeniedException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ public void checkAnyActionOnTable(UserIdentity currentUser, Set<Long> roleIds, T
PrivilegeType.ANY);
}

@Override
public void checkColumnsAction(UserIdentity currentUser, Set<Long> roleIds, TableName tableName,
String column, PrivilegeType privilegeType) throws AccessDeniedException {
hasPermission(RangerHiveResource.builder()
.setDatabase(tableName.getDb())
.setTable(tableName.getTbl())
.setColumn(column)
.build(),
currentUser, privilegeType);
}

@Override
public Map<String, Expr> getColumnMaskingPolicy(ConnectContext context, TableName tableName, List<Column> columns) {
Map<String, Expr> maskingExprMap = Maps.newHashMap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,18 @@ public void checkAnyActionOnAnyTable(UserIdentity currentUser, Set<Long> roleIds
throw new AccessDeniedException();
}

@Override
public void checkColumnsAction(UserIdentity currentUser, Set<Long> roleIds, TableName tableName,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public void checkColumnsAction(UserIdentity currentUser, Set<Long> roleIds, TableName tableName,
public void checkColumnAction(UserIdentity currentUser, Set<Long> roleIds, TableName tableName,

String column, PrivilegeType privilegeType) throws AccessDeniedException {
hasPermission(RangerStarRocksResource.builder()
.setCatalog(tableName.getCatalog())
.setDatabase(tableName.getDb())
.setTable(tableName.getTbl())
.setColumn(column)
.build(),
currentUser, privilegeType);
}

@Override
public void checkViewAction(UserIdentity currentUser, Set<Long> roleIds, TableName tableName, PrivilegeType privilegeType)
throws AccessDeniedException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public static ExecPlan plan(StatementBase stmt, ConnectContext session,
QueryStatement queryStmt = (QueryStatement) stmt;
resultSinkType = queryStmt.hasOutFileClause() ? TResultSinkType.FILE : resultSinkType;
boolean isOnlyOlapTableQueries = AnalyzerUtils.isOnlyHasOlapTables(queryStmt);
needWholePhaseLock = isLockFree(isOnlyOlapTableQueries, session) ? false : true;
needWholePhaseLock = isLockFree(isOnlyOlapTableQueries, session) ? false : true;
ExecPlan plan;
if (needWholePhaseLock) {
plan = createQueryPlan(queryStmt, session, resultSinkType);
Expand Down Expand Up @@ -186,7 +186,7 @@ private static boolean isLockFree(boolean isOnlyOlapTable, ConnectContext sessio
/**
* Create a map from opt expression to parse node for the optimizer to use which only used in text match rewrite for mv.
*/
private static Map<Operator, ParseNode> makeOptToAstMap(SessionVariable sessionVariable) {
public static Map<Operator, ParseNode> makeOptToAstMap(SessionVariable sessionVariable) {
if (sessionVariable.isEnableMaterializedViewTextMatchRewrite()) {
return Maps.newHashMap();
}
Expand Down Expand Up @@ -225,6 +225,7 @@ private static ExecPlan createQueryPlan(StatementBase stmt,
new ColumnRefSet(logicalPlan.getOutputColumn()),
columnRefFactory);
}

try (Timer ignored = Tracers.watchScope("ExecPlanBuild")) {
// 3. Build fragment exec plan
/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,10 @@ public static void checkAnyActionOnTable(UserIdentity currentUser, Set<Long> rol
}

public static void checkColumnsAction(UserIdentity currentUser, Set<Long> roleIds,
TableName tableName, Set<String> columns,
TableName tableName, String column,
PrivilegeType privilegeType) throws AccessDeniedException {
getInstance().getAccessControlOrDefault(tableName.getCatalog()).checkColumnsAction(currentUser, roleIds,
tableName, columns, privilegeType);
tableName, column, privilegeType);
}

public static void checkViewAction(UserIdentity currentUser, Set<Long> roleIds, TableName tableName,
Expand Down
Loading
Loading