diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/RangerAccessController.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/RangerAccessController.java index 30b42e19976477..5f49f4d5735914 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/RangerAccessController.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/RangerAccessController.java @@ -17,15 +17,27 @@ package org.apache.doris.catalog.authorizer.ranger; +import org.apache.doris.analysis.UserIdentity; import org.apache.doris.common.AuthorizationException; import org.apache.doris.mysql.privilege.CatalogAccessController; +import org.apache.doris.mysql.privilege.DataMaskPolicy; +import org.apache.doris.mysql.privilege.RangerDataMaskPolicy; +import org.apache.doris.mysql.privilege.RangerRowFilterPolicy; +import org.apache.doris.mysql.privilege.RowFilterPolicy; +import com.google.common.collect.Lists; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl; +import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl; import org.apache.ranger.plugin.policyengine.RangerAccessResult; +import org.apache.ranger.plugin.policyengine.RangerAccessResultProcessor; +import org.apache.ranger.plugin.service.RangerBasePlugin; import java.util.Collection; +import java.util.List; +import java.util.Optional; public abstract class RangerAccessController implements CatalogAccessController { private static final Logger LOG = LogManager.getLogger(RangerAccessController.class); @@ -74,4 +86,70 @@ public static void checkRequestResults(Collection results, S } } } + + @Override + public List evalRowFilterPolicies(UserIdentity currentUser, String ctl, String db, + String tbl) { + RangerAccessResourceImpl resource = createResource(ctl, db, tbl); + RangerAccessRequestImpl request = createRequest(currentUser); + request.setResource(resource); + + if (LOG.isDebugEnabled()) { + LOG.debug("ranger request: {}", request); + } + List res = Lists.newArrayList(); + RangerAccessResult policy = getPlugin().evalRowFilterPolicies(request, getAccessResultProcessor()); + if (LOG.isDebugEnabled()) { + LOG.debug("ranger response: {}", policy); + } + if (policy == null) { + return res; + } + String filterExpr = policy.getFilterExpr(); + if (StringUtils.isEmpty(filterExpr)) { + return res; + } + res.add(new RangerRowFilterPolicy(currentUser, ctl, db, tbl, policy.getPolicyId(), policy.getPolicyVersion(), + filterExpr)); + return res; + } + + @Override + public Optional evalDataMaskPolicy(UserIdentity currentUser, String ctl, String db, String tbl, + String col) { + RangerAccessResourceImpl resource = createResource(ctl, db, tbl, col); + RangerAccessRequestImpl request = createRequest(currentUser); + request.setResource(resource); + + if (LOG.isDebugEnabled()) { + LOG.debug("ranger request: {}", request); + } + RangerAccessResult policy = getPlugin().evalDataMaskPolicies(request, getAccessResultProcessor()); + if (LOG.isDebugEnabled()) { + LOG.debug("ranger response: {}", policy); + } + if (policy == null) { + return Optional.empty(); + } + String maskType = policy.getMaskType(); + if (StringUtils.isEmpty(maskType)) { + return Optional.empty(); + } + String transformer = policy.getMaskTypeDef().getTransformer(); + if (StringUtils.isEmpty(transformer)) { + return Optional.empty(); + } + return Optional.of(new RangerDataMaskPolicy(currentUser, ctl, db, tbl, col, policy.getPolicyId(), + policy.getPolicyVersion(), maskType, transformer.replace("{col}", col))); + } + + protected abstract RangerAccessRequestImpl createRequest(UserIdentity currentUser); + + protected abstract RangerAccessResourceImpl createResource(String ctl, String db, String tbl); + + protected abstract RangerAccessResourceImpl createResource(String ctl, String db, String tbl, String col); + + protected abstract RangerBasePlugin getPlugin(); + + protected abstract RangerAccessResultProcessor getAccessResultProcessor(); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/doris/RangerDorisAccessController.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/doris/RangerDorisAccessController.java index 2f9ef5b8210367..f8c38ba78e9da5 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/doris/RangerDorisAccessController.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/doris/RangerDorisAccessController.java @@ -21,7 +21,6 @@ import org.apache.doris.analysis.UserIdentity; import org.apache.doris.catalog.Env; import org.apache.doris.catalog.authorizer.ranger.RangerAccessController; -import org.apache.doris.catalog.authorizer.ranger.hive.RangerHiveResource; import org.apache.doris.cluster.ClusterNamespace; import org.apache.doris.common.AuthorizationException; import org.apache.doris.mysql.privilege.PrivPredicate; @@ -31,6 +30,8 @@ import org.apache.ranger.plugin.policyengine.RangerAccessRequest; import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl; import org.apache.ranger.plugin.policyengine.RangerAccessResult; +import org.apache.ranger.plugin.policyengine.RangerAccessResultProcessor; +import org.apache.ranger.plugin.service.RangerBasePlugin; import java.util.ArrayList; import java.util.Collection; @@ -54,14 +55,20 @@ public RangerDorisAccessController(String serviceName) { } private RangerAccessRequestImpl createRequest(UserIdentity currentUser, DorisAccessType accessType) { + RangerAccessRequestImpl request = createRequest(currentUser); + request.setAction(accessType.name()); + request.setAccessType(accessType.name()); + return request; + } + + @Override + protected RangerAccessRequestImpl createRequest(UserIdentity currentUser) { RangerAccessRequestImpl request = new RangerAccessRequestImpl(); request.setUser(ClusterNamespace.getNameFromFullName(currentUser.getQualifiedUser())); Set roles = Env.getCurrentEnv().getAuth().getRolesByUser(currentUser, false); request.setUserRoles(roles.stream().map(role -> ClusterNamespace.getNameFromFullName(role)).collect( Collectors.toSet())); - request.setAction(accessType.name()); - request.setAccessType(accessType.name()); request.setClientIPAddress(currentUser.getHost()); request.setClusterType(CLIENT_TYPE_DORIS); request.setClientType(CLIENT_TYPE_DORIS); @@ -96,27 +103,6 @@ private boolean checkPrivilege(UserIdentity currentUser, DorisAccessType accessT return checkRequestResult(request, result, accessType.name()); } - public String getFilterExpr(UserIdentity currentUser, DorisAccessType accessType, - RangerHiveResource resource) { - RangerAccessRequestImpl request = createRequest(currentUser, accessType); - request.setResource(resource); - RangerAccessResult result = dorisPlugin.isAccessAllowed(request); - - return result.getFilterExpr(); - } - - public void getColumnMask(UserIdentity currentUser, DorisAccessType accessType, - RangerHiveResource resource) { - RangerAccessRequestImpl request = createRequest(currentUser, accessType); - request.setResource(resource); - RangerAccessResult result = dorisPlugin.isAccessAllowed(request); - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("maskType: %s, maskTypeDef: %s, maskedValue: %s", result.getMaskType(), - result.getMaskTypeDef(), result.getMaskedValue())); - } - } - @Override public boolean checkGlobalPriv(UserIdentity currentUser, PrivPredicate wanted) { // ranger does not support global privilege, @@ -159,7 +145,7 @@ public void checkColsPriv(UserIdentity currentUser, String ctl, String db, Strin @Override public boolean checkCloudPriv(UserIdentity currentUser, String resourceName, - PrivPredicate wanted, ResourceTypeEnum type) { + PrivPredicate wanted, ResourceTypeEnum type) { return false; } @@ -175,6 +161,28 @@ public boolean checkWorkloadGroupPriv(UserIdentity currentUser, String workloadG return checkPrivilege(currentUser, DorisAccessType.toAccessType(wanted), resource); } + @Override + protected RangerDorisResource createResource(String ctl, String db, String tbl) { + return new RangerDorisResource(DorisObjectType.TABLE, + ctl, ClusterNamespace.getNameFromFullName(db), tbl); + } + + @Override + protected RangerDorisResource createResource(String ctl, String db, String tbl, String col) { + return new RangerDorisResource(DorisObjectType.COLUMN, + ctl, ClusterNamespace.getNameFromFullName(db), tbl, col); + } + + @Override + protected RangerBasePlugin getPlugin() { + return dorisPlugin; + } + + @Override + protected RangerAccessResultProcessor getAccessResultProcessor() { + return null; + } + // For test only public static void main(String[] args) { RangerDorisAccessController ac = new RangerDorisAccessController("doris"); diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessController.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessController.java index 3e7d3a89cd7f61..8c7f672bdcbf1b 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessController.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessController.java @@ -25,22 +25,25 @@ import org.apache.doris.common.AuthorizationException; import org.apache.doris.common.ThreadPoolManager; import org.apache.doris.datasource.InternalCatalog; +import org.apache.doris.mysql.privilege.DataMaskPolicy; import org.apache.doris.mysql.privilege.PrivPredicate; import com.google.common.collect.Maps; -import org.apache.hadoop.hive.ql.security.authorization.plugin.HiveAccessControlException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.ranger.plugin.policyengine.RangerAccessRequest; import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl; import org.apache.ranger.plugin.policyengine.RangerAccessResult; +import org.apache.ranger.plugin.policyengine.RangerAccessResultProcessor; import org.apache.ranger.plugin.policyengine.RangerPolicyEngine; +import org.apache.ranger.plugin.service.RangerBasePlugin; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -57,23 +60,28 @@ public RangerHiveAccessController(Map properties) { String serviceName = properties.get("ranger.service.name"); hivePlugin = new RangerHivePlugin(serviceName); auditHandler = new RangerHiveAuditHandler(hivePlugin.getConfig()); - //start a timed log flusher + // start a timed log flusher logFlushTimer.scheduleAtFixedRate(new RangerHiveAuditLogFlusher(auditHandler), 10, 20L, TimeUnit.SECONDS); } private RangerAccessRequestImpl createRequest(UserIdentity currentUser, HiveAccessType accessType) { + RangerAccessRequestImpl request = createRequest(currentUser); + if (accessType == HiveAccessType.USE) { + request.setAccessType(RangerPolicyEngine.ANY_ACCESS); + } else { + request.setAccessType(accessType.name().toLowerCase()); + } + return request; + } + + @Override + protected RangerAccessRequestImpl createRequest(UserIdentity currentUser) { RangerAccessRequestImpl request = new RangerAccessRequestImpl(); String user = currentUser.getQualifiedUser(); request.setUser(ClusterNamespace.getNameFromFullName(user)); Set roles = Env.getCurrentEnv().getAuth().getRolesByUser(currentUser, false); request.setUserRoles(roles.stream().map(role -> ClusterNamespace.getNameFromFullName(role)).collect( Collectors.toSet())); - request.setAction(accessType.name()); - if (accessType == HiveAccessType.USE) { - request.setAccessType(RangerPolicyEngine.ANY_ACCESS); - } else { - request.setAccessType(accessType.name().toLowerCase()); - } request.setClientIPAddress(currentUser.getHost()); request.setClusterType(CLIENT_TYPE_DORIS); request.setClientType(CLIENT_TYPE_DORIS); @@ -104,27 +112,6 @@ private boolean checkPrivilege(UserIdentity currentUser, HiveAccessType accessTy return checkRequestResult(request, result, accessType.name()); } - public String getFilterExpr(UserIdentity currentUser, HiveAccessType accessType, - RangerHiveResource resource) throws HiveAccessControlException { - RangerAccessRequestImpl request = createRequest(currentUser, accessType); - request.setResource(resource); - RangerAccessResult result = hivePlugin.isAccessAllowed(request, auditHandler); - - return result.getFilterExpr(); - } - - public void getColumnMask(UserIdentity currentUser, HiveAccessType accessType, - RangerHiveResource resource) { - RangerAccessRequestImpl request = createRequest(currentUser, accessType); - request.setResource(resource); - RangerAccessResult result = hivePlugin.isAccessAllowed(request, auditHandler); - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("maskType: %s, maskTypeDef: %s, maskedValue: %s", result.getMaskType(), - result.getMaskTypeDef(), result.getMaskedValue())); - } - } - private HiveAccessType convertToAccessType(PrivPredicate predicate) { if (predicate == PrivPredicate.SHOW) { return HiveAccessType.USE; @@ -187,10 +174,16 @@ public void checkColsPriv(UserIdentity currentUser, String ctl, String db, Strin @Override public boolean checkCloudPriv(UserIdentity currentUser, String resourceName, - PrivPredicate wanted, ResourceTypeEnum type) { + PrivPredicate wanted, ResourceTypeEnum type) { return false; } + @Override + public Optional evalDataMaskPolicy(UserIdentity currentUser, String ctl, String db, String tbl, + String col) { + return Optional.empty(); + } + @Override public boolean checkResourcePriv(UserIdentity currentUser, String resourceName, PrivPredicate wanted) { return false; @@ -201,6 +194,28 @@ public boolean checkWorkloadGroupPriv(UserIdentity currentUser, String workloadG return false; } + @Override + protected RangerHiveResource createResource(String ctl, String db, String tbl) { + return new RangerHiveResource(HiveObjectType.TABLE, + ClusterNamespace.getNameFromFullName(db), tbl); + } + + @Override + protected RangerHiveResource createResource(String ctl, String db, String tbl, String col) { + return new RangerHiveResource(HiveObjectType.COLUMN, + ClusterNamespace.getNameFromFullName(db), tbl, col); + } + + @Override + protected RangerBasePlugin getPlugin() { + return hivePlugin; + } + + @Override + protected RangerAccessResultProcessor getAccessResultProcessor() { + return auditHandler; + } + // For test only public static void main(String[] args) { Map properties = Maps.newHashMap(); diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java index 1223778a67eb73..cc0cf619042f75 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java @@ -23,6 +23,7 @@ import org.apache.doris.catalog.AuthorizationInfo; import org.apache.doris.catalog.Env; import org.apache.doris.catalog.authorizer.ranger.doris.RangerDorisAccessController; +import org.apache.doris.common.AnalysisException; import org.apache.doris.common.Config; import org.apache.doris.common.UserException; import org.apache.doris.datasource.CatalogIf; @@ -36,7 +37,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.Set; /** @@ -229,7 +233,7 @@ public boolean checkCloudPriv(ConnectContext ctx, String cloudName, PrivPredicat } public boolean checkCloudPriv(UserIdentity currentUser, String cloudName, - PrivPredicate wanted, ResourceTypeEnum type) { + PrivPredicate wanted, ResourceTypeEnum type) { return defaultAccessController.checkCloudPriv(currentUser, cloudName, wanted, type); } @@ -260,4 +264,32 @@ public boolean checkPrivByAuthInfo(ConnectContext ctx, AuthorizationInfo authInf } return true; } + + public Map> evalDataMaskPolicies(UserIdentity currentUser, String + ctl, String db, String tbl, Set cols) { + Map> res = Maps.newHashMap(); + for (String col : cols) { + res.put(col, evalDataMaskPolicy(currentUser, ctl, db, tbl, col)); + } + return res; + } + + public Optional evalDataMaskPolicy(UserIdentity currentUser, String + ctl, String db, String tbl, String col) { + Objects.requireNonNull(currentUser, "require currentUser object"); + Objects.requireNonNull(ctl, "require ctl object"); + Objects.requireNonNull(db, "require db object"); + Objects.requireNonNull(tbl, "require tbl object"); + Objects.requireNonNull(col, "require col object"); + return getAccessControllerOrDefault(ctl).evalDataMaskPolicy(currentUser, ctl, db, tbl, col.toLowerCase()); + } + + public List evalRowFilterPolicies(UserIdentity currentUser, String + ctl, String db, String tbl) throws AnalysisException { + Objects.requireNonNull(currentUser, "require currentUser object"); + Objects.requireNonNull(ctl, "require ctl object"); + Objects.requireNonNull(db, "require db object"); + Objects.requireNonNull(tbl, "require tbl object"); + return getAccessControllerOrDefault(ctl).evalRowFilterPolicies(currentUser, ctl, db, tbl); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogAccessController.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogAccessController.java index f9d4cdbd03c88a..57af911e61d4fc 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogAccessController.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogAccessController.java @@ -19,8 +19,11 @@ import org.apache.doris.analysis.ResourceTypeEnum; import org.apache.doris.analysis.UserIdentity; +import org.apache.doris.common.AnalysisException; import org.apache.doris.common.AuthorizationException; +import java.util.List; +import java.util.Optional; import java.util.Set; public interface CatalogAccessController { @@ -77,4 +80,10 @@ void checkColsPriv(UserIdentity currentUser, String ctl, String db, String tbl, // ==== Cloud ==== boolean checkCloudPriv(UserIdentity currentUser, String resourceName, PrivPredicate wanted, ResourceTypeEnum type); + + Optional evalDataMaskPolicy(UserIdentity currentUser, String ctl, String db, String tbl, + String col); + + List evalRowFilterPolicies(UserIdentity currentUser, String ctl, String db, String tbl) + throws AnalysisException; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DataMaskPolicy.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DataMaskPolicy.java new file mode 100644 index 00000000000000..ca22129628e445 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DataMaskPolicy.java @@ -0,0 +1,24 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.doris.mysql.privilege; + +public interface DataMaskPolicy { + String getMaskTypeDef(); + + String getPolicyIdent(); +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/InternalAccessController.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/InternalAccessController.java index 0e3fac621347aa..219c68ad78c608 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/InternalAccessController.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/InternalAccessController.java @@ -19,8 +19,18 @@ import org.apache.doris.analysis.ResourceTypeEnum; import org.apache.doris.analysis.UserIdentity; +import org.apache.doris.catalog.Database; +import org.apache.doris.catalog.Env; +import org.apache.doris.catalog.Table; +import org.apache.doris.common.AnalysisException; import org.apache.doris.common.AuthorizationException; +import org.apache.doris.datasource.InternalCatalog; +import org.apache.doris.policy.PolicyMgr; +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.Optional; import java.util.Set; public class InternalAccessController implements CatalogAccessController { @@ -68,7 +78,27 @@ public boolean checkWorkloadGroupPriv(UserIdentity currentUser, String workloadG @Override public boolean checkCloudPriv(UserIdentity currentUser, String resourceName, - PrivPredicate wanted, ResourceTypeEnum type) { + PrivPredicate wanted, ResourceTypeEnum type) { return auth.checkResourcePriv(currentUser, resourceName, wanted); } + + @Override + public Optional evalDataMaskPolicy(UserIdentity currentUser, String ctl, String db, String tbl, + String col) { + return Optional.empty(); + } + + @Override + public List evalRowFilterPolicies(UserIdentity currentUser, String ctl, String db, + String tbl) + throws AnalysisException { + // current not support external catalog + if (!InternalCatalog.INTERNAL_CATALOG_NAME.equals(ctl)) { + return Lists.newArrayList(); + } + PolicyMgr policyMgr = Env.getCurrentEnv().getPolicyMgr(); + Database database = Env.getCurrentEnv().getInternalCatalog().getDbOrAnalysisException(db); + Table table = database.getTableOrAnalysisException(tbl); + return policyMgr.getUserPolicies(database.getId(), table.getId(), currentUser); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RangerDataMaskPolicy.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RangerDataMaskPolicy.java new file mode 100644 index 00000000000000..91010f80cb378f --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RangerDataMaskPolicy.java @@ -0,0 +1,103 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.doris.mysql.privilege; + +import org.apache.doris.analysis.UserIdentity; + +public class RangerDataMaskPolicy implements DataMaskPolicy { + private UserIdentity userIdentity; + private String ctl; + private String db; + private String tbl; + private String col; + private long policyId; + private long policyVersion; + private String maskType; + private String maskTypeDef; + + public RangerDataMaskPolicy(UserIdentity userIdentity, String ctl, String db, String tbl, String col, + long policyId, + long policyVersion, String maskType, String maskTypeDef) { + this.userIdentity = userIdentity; + this.ctl = ctl; + this.db = db; + this.tbl = tbl; + this.col = col; + this.policyId = policyId; + this.policyVersion = policyVersion; + this.maskType = maskType; + this.maskTypeDef = maskTypeDef; + } + + public UserIdentity getUserIdentity() { + return userIdentity; + } + + public String getCtl() { + return ctl; + } + + public String getDb() { + return db; + } + + public String getTbl() { + return tbl; + } + + public String getCol() { + return col; + } + + public long getPolicyId() { + return policyId; + } + + public long getPolicyVersion() { + return policyVersion; + } + + public String getMaskType() { + return maskType; + } + + @Override + public String getMaskTypeDef() { + return maskTypeDef; + } + + @Override + public String getPolicyIdent() { + return getPolicyId() + ":" + getPolicyVersion(); + } + + @Override + public String toString() { + return "RangerDataMaskPolicy{" + + "userIdentity=" + userIdentity + + ", ctl='" + ctl + '\'' + + ", db='" + db + '\'' + + ", tbl='" + tbl + '\'' + + ", col='" + col + '\'' + + ", policyId=" + policyId + + ", policyVersion=" + policyVersion + + ", maskType='" + maskType + '\'' + + ", maskTypeDef='" + maskTypeDef + '\'' + + '}'; + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RangerRowFilterPolicy.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RangerRowFilterPolicy.java new file mode 100644 index 00000000000000..661efcf8a4a852 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RangerRowFilterPolicy.java @@ -0,0 +1,95 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.doris.mysql.privilege; + +import org.apache.doris.analysis.UserIdentity; +import org.apache.doris.nereids.parser.NereidsParser; +import org.apache.doris.nereids.trees.expressions.Expression; + +public class RangerRowFilterPolicy implements RowFilterPolicy { + private UserIdentity userIdentity; + private String ctl; + private String db; + private String tbl; + private long policyId; + private long policyVersion; + private String filterExpr; + + public RangerRowFilterPolicy(UserIdentity userIdentity, String ctl, String db, String tbl, long policyId, + long policyVersion, String filterExpr) { + this.userIdentity = userIdentity; + this.ctl = ctl; + this.db = db; + this.tbl = tbl; + this.policyId = policyId; + this.policyVersion = policyVersion; + this.filterExpr = filterExpr; + } + + public UserIdentity getUserIdentity() { + return userIdentity; + } + + public String getCtl() { + return ctl; + } + + public String getDb() { + return db; + } + + public String getTbl() { + return tbl; + } + + public long getPolicyId() { + return policyId; + } + + public long getPolicyVersion() { + return policyVersion; + } + + public String getFilterExpr() { + return filterExpr; + } + + @Override + public Expression getFilterExpression() { + NereidsParser nereidsParser = new NereidsParser(); + return nereidsParser.parseExpression(filterExpr); + } + + @Override + public String getPolicyIdent() { + return getPolicyId() + ":" + getPolicyVersion(); + } + + @Override + public String toString() { + return "RangerRowFilterPolicy{" + + "userIdentity=" + userIdentity + + ", ctl='" + ctl + '\'' + + ", db='" + db + '\'' + + ", tbl='" + tbl + '\'' + + ", policyId=" + policyId + + ", policyVersion=" + policyVersion + + ", filterExpr='" + filterExpr + '\'' + + '}'; + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RowFilterPolicy.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RowFilterPolicy.java new file mode 100644 index 00000000000000..678a1927e243dc --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RowFilterPolicy.java @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.doris.mysql.privilege; + +import org.apache.doris.common.AnalysisException; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.policy.FilterType; + +public interface RowFilterPolicy { + default FilterType getFilterType() { + return FilterType.RESTRICTIVE; + } + + Expression getFilterExpression() throws AnalysisException; + + String getPolicyIdent(); +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCheckPolicy.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCheckPolicy.java index 5630153c0ced4a..e7ed8bf20ad1a0 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCheckPolicy.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCheckPolicy.java @@ -18,9 +18,10 @@ package org.apache.doris.nereids.trees.plans.logical; import org.apache.doris.analysis.UserIdentity; +import org.apache.doris.mysql.privilege.AccessControllerManager; +import org.apache.doris.mysql.privilege.RowFilterPolicy; import org.apache.doris.nereids.exceptions.AnalysisException; import org.apache.doris.nereids.memo.GroupExpression; -import org.apache.doris.nereids.parser.NereidsParser; import org.apache.doris.nereids.properties.LogicalProperties; import org.apache.doris.nereids.trees.expressions.And; import org.apache.doris.nereids.trees.expressions.Expression; @@ -29,12 +30,9 @@ import org.apache.doris.nereids.trees.plans.PlanType; import org.apache.doris.nereids.trees.plans.PropagateFuncDeps; import org.apache.doris.nereids.trees.plans.algebra.CatalogRelation; -import org.apache.doris.nereids.trees.plans.commands.CreatePolicyCommand; import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor; import org.apache.doris.nereids.util.ExpressionUtils; import org.apache.doris.nereids.util.Utils; -import org.apache.doris.policy.PolicyMgr; -import org.apache.doris.policy.RowPolicy; import org.apache.doris.qe.ConnectContext; import com.google.common.base.Preconditions; @@ -125,39 +123,45 @@ public Optional getFilter(LogicalRelation logicalRelation, ConnectCo return Optional.empty(); } - PolicyMgr policyMgr = connectContext.getEnv().getPolicyMgr(); + AccessControllerManager accessManager = connectContext.getEnv().getAccessManager(); UserIdentity currentUserIdentity = connectContext.getCurrentUserIdentity(); if (currentUserIdentity.isRootUser() || currentUserIdentity.isAdminUser()) { return Optional.empty(); } CatalogRelation catalogRelation = (CatalogRelation) logicalRelation; - long dbId = catalogRelation.getDatabase().getId(); - long tableId = catalogRelation.getTable().getId(); - List policies = policyMgr.getUserPolicies(dbId, tableId, currentUserIdentity); + String ctlName = catalogRelation.getDatabase().getCatalog().getName(); + String dbName = catalogRelation.getDatabase().getFullName(); + String tableName = catalogRelation.getTable().getName(); + List policies = null; + try { + policies = accessManager.evalRowFilterPolicies(currentUserIdentity, ctlName, + dbName, tableName); + } catch (org.apache.doris.common.AnalysisException e) { + throw new AnalysisException(e.getMessage(), e); + } if (policies.isEmpty()) { return Optional.empty(); } return Optional.ofNullable(mergeRowPolicy(policies)); } - private Expression mergeRowPolicy(List policies) { + private Expression mergeRowPolicy(List policies) { List orList = new ArrayList<>(); List andList = new ArrayList<>(); - for (RowPolicy policy : policies) { - String sql = policy.getOriginStmt(); - NereidsParser nereidsParser = new NereidsParser(); - CreatePolicyCommand command = (CreatePolicyCommand) nereidsParser.parseSingle(sql); - Optional wherePredicate = command.getWherePredicate(); - if (!wherePredicate.isPresent()) { - throw new AnalysisException("Invalid row policy [" + policy.getPolicyName() + "], " + sql); + for (RowFilterPolicy policy : policies) { + Expression wherePredicate = null; + try { + wherePredicate = policy.getFilterExpression(); + } catch (org.apache.doris.common.AnalysisException e) { + throw new AnalysisException(e.getMessage(), e); } switch (policy.getFilterType()) { case PERMISSIVE: - orList.add(wherePredicate.get()); + orList.add(wherePredicate); break; case RESTRICTIVE: - andList.add(wherePredicate.get()); + andList.add(wherePredicate); break; default: throw new IllegalStateException("Invalid operator"); diff --git a/fe/fe-core/src/main/java/org/apache/doris/policy/RowPolicy.java b/fe/fe-core/src/main/java/org/apache/doris/policy/RowPolicy.java index d69468d9d4333a..86dbeca28d25fa 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/policy/RowPolicy.java +++ b/fe/fe-core/src/main/java/org/apache/doris/policy/RowPolicy.java @@ -29,6 +29,10 @@ import org.apache.doris.catalog.Table; import org.apache.doris.common.AnalysisException; import org.apache.doris.common.util.SqlParserUtils; +import org.apache.doris.mysql.privilege.RowFilterPolicy; +import org.apache.doris.nereids.parser.NereidsParser; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.plans.commands.CreatePolicyCommand; import org.apache.doris.qe.ShowResultSetMetaData; import com.google.common.collect.Lists; @@ -42,12 +46,13 @@ import java.io.StringReader; import java.util.List; import java.util.Objects; +import java.util.Optional; /** * Save policy for filtering data. **/ @Data -public class RowPolicy extends Policy { +public class RowPolicy extends Policy implements RowFilterPolicy { public static final ShowResultSetMetaData ROW_META_DATA = ShowResultSetMetaData.builder() @@ -186,4 +191,22 @@ public boolean matchPolicy(DropPolicyLog checkedDropPolicyLogCondition) { public boolean isInvalid() { return (wherePredicate == null); } + + @Override + public Expression getFilterExpression() throws AnalysisException { + NereidsParser nereidsParser = new NereidsParser(); + String sql = getOriginStmt(); + CreatePolicyCommand command = (CreatePolicyCommand) nereidsParser.parseSingle(sql); + Optional wherePredicate = command.getWherePredicate(); + if (!wherePredicate.isPresent()) { + throw new AnalysisException("Invalid row policy [" + getPolicyIdent() + "], " + sql); + } + return wherePredicate.get(); + } + + @Override + public String getPolicyIdent() { + return getPolicyName(); + } + } diff --git a/fe/fe-core/src/test/java/org/apache/doris/datasource/ColumnPrivTest.java b/fe/fe-core/src/test/java/org/apache/doris/datasource/ColumnPrivTest.java index 690649fae7d1c4..967c79f88f5fca 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/datasource/ColumnPrivTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/datasource/ColumnPrivTest.java @@ -33,13 +33,16 @@ import org.apache.doris.catalog.Env; import org.apache.doris.catalog.PrimitiveType; import org.apache.doris.cluster.ClusterNamespace; +import org.apache.doris.common.AnalysisException; import org.apache.doris.common.AuthorizationException; import org.apache.doris.common.FeConstants; import org.apache.doris.datasource.test.TestExternalCatalog.TestCatalogProvider; import org.apache.doris.mysql.privilege.AccessControllerFactory; import org.apache.doris.mysql.privilege.Auth; import org.apache.doris.mysql.privilege.CatalogAccessController; +import org.apache.doris.mysql.privilege.DataMaskPolicy; import org.apache.doris.mysql.privilege.PrivPredicate; +import org.apache.doris.mysql.privilege.RowFilterPolicy; import org.apache.doris.qe.ConnectContext; import org.apache.doris.qe.ShowExecutor; import org.apache.doris.qe.ShowResultSet; @@ -54,6 +57,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; // when `select` suppport `col auth`,will open ColumnPrivTest @@ -320,6 +324,18 @@ public void checkColsPriv(UserIdentity currentUser, String ctl, String db, Strin public boolean checkCloudPriv(UserIdentity currentUser, String resourceName, PrivPredicate wanted, ResourceTypeEnum type) { return false; } + + @Override + public Optional evalDataMaskPolicy(UserIdentity currentUser, String ctl, String db, + String tbl, String col) { + return Optional.empty(); + } + + @Override + public List evalRowFilterPolicies(UserIdentity currentUser, String ctl, + String db, String tbl) throws AnalysisException { + return null; + } } } diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/privileges/TestCheckPrivileges.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/privileges/TestCheckPrivileges.java index f9f6eb9100238e..cfba61b0cd763b 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/privileges/TestCheckPrivileges.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/privileges/TestCheckPrivileges.java @@ -29,7 +29,9 @@ import org.apache.doris.mysql.privilege.AccessControllerFactory; import org.apache.doris.mysql.privilege.AccessControllerManager; import org.apache.doris.mysql.privilege.CatalogAccessController; +import org.apache.doris.mysql.privilege.DataMaskPolicy; import org.apache.doris.mysql.privilege.PrivPredicate; +import org.apache.doris.mysql.privilege.RowFilterPolicy; import org.apache.doris.nereids.exceptions.AnalysisException; import org.apache.doris.nereids.util.PlanChecker; import org.apache.doris.utframe.TestWithFeService; @@ -46,6 +48,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; public class TestCheckPrivileges extends TestWithFeService { @@ -305,6 +308,18 @@ public boolean checkCloudPriv(UserIdentity currentUser, String resourceName, Pri ResourceTypeEnum type) { return true; } + + @Override + public Optional evalDataMaskPolicy(UserIdentity currentUser, String ctl, String db, String tbl, + String col) { + return Optional.empty(); + } + + @Override + public List evalRowFilterPolicies(UserIdentity currentUser, String ctl, String db, + String tbl) throws org.apache.doris.common.AnalysisException { + return Lists.newArrayList(); + } } private static class MakePrivileges {