From 930861876a9d37cec831952e39ad0e1c05097338 Mon Sep 17 00:00:00 2001 From: Yulei-Yang Date: Sun, 5 Mar 2023 10:56:28 +0800 Subject: [PATCH 1/3] finish debug of hms ranger plugin --- docs/en/docs/lakehouse/multi-catalog/hive.md | 18 ++++ .../docs/lakehouse/multi-catalog/hive.md | 16 ++++ .../RangerHiveAccessController.java | 85 +++++++++++-------- .../authorizer/RangerHiveAuditLogFlusher.java | 42 +++++++++ 4 files changed, 127 insertions(+), 34 deletions(-) create mode 100644 fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/RangerHiveAuditLogFlusher.java diff --git a/docs/en/docs/lakehouse/multi-catalog/hive.md b/docs/en/docs/lakehouse/multi-catalog/hive.md index ee73c6d501754b..0692517cadc258 100644 --- a/docs/en/docs/lakehouse/multi-catalog/hive.md +++ b/docs/en/docs/lakehouse/multi-catalog/hive.md @@ -117,6 +117,24 @@ CREATE CATALOG hive PROPERTIES ( ); ``` + + +when connecting to Hive Metastore which is authorized by Ranger, need some properties and update FE runtime environment. + +1. add below properties when creating Catalog: + +```sql +"access_controller.properties.ranger.service.name" = "", +"access_controller.class" = "org.apache.doris.catalog.authorizer.RangerHiveAccessControllerFactory", +``` + +2. update all FEs' runtime environment: + a. copy all ranger-*.xml files to /conf which are located in HMS/conf directory + b. update value of `ranger.plugin.hive.policy.cache.dir` in ranger--security.xml to a writable directory + c. restart FE + + + In Doris 1.2.1 and newer, you can create a Resource that contains all these parameters, and reuse the Resource when creating new Catalogs. Here is an example: ```sql diff --git a/docs/zh-CN/docs/lakehouse/multi-catalog/hive.md b/docs/zh-CN/docs/lakehouse/multi-catalog/hive.md index 3b14d0e8a31a3a..c168cfc57b1ea5 100644 --- a/docs/zh-CN/docs/lakehouse/multi-catalog/hive.md +++ b/docs/zh-CN/docs/lakehouse/multi-catalog/hive.md @@ -113,6 +113,22 @@ CREATE CATALOG hive PROPERTIES ( ); ``` + + +连接开启 Ranger 权限校验的 Hive Metastore 需要增加配置 & 配置环境: +1. 创建 Catalog 时增加: + +```sql +"access_controller.properties.ranger.service.name" = "", +"access_controller.class" = "org.apache.doris.catalog.authorizer.RangerHiveAccessControllerFactory", +``` +2. 配置所有 FE 环境: + a. 将 HMS conf 目录下的三个 Ranger 配置文件Copy到 /conf 目录下 + b. 修改其中 ranger--security.xml 的属性 `ranger.plugin.hive.policy.cache.dir` 的值为一个可写目录 + c. 重启 FE + + + 在 1.2.1 版本之后,我们也可以将这些信息通过创建一个 Resource 统一存储,然后在创建 Catalog 时使用这个 Resource。示例如下: ```sql diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/RangerHiveAccessController.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/RangerHiveAccessController.java index 2c819334de8c40..24ba399d5ffaee 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/RangerHiveAccessController.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/RangerHiveAccessController.java @@ -19,6 +19,7 @@ import org.apache.doris.analysis.UserIdentity; import org.apache.doris.common.AuthorizationException; +import org.apache.doris.common.ThreadPoolManager; import org.apache.doris.mysql.privilege.CatalogAccessController; import org.apache.doris.mysql.privilege.PrivPredicate; @@ -33,13 +34,18 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; public class RangerHiveAccessController implements CatalogAccessController { public static final String CLIENT_TYPE_DORIS = "doris"; private static final Logger LOG = LogManager.getLogger(RangerHiveAccessController.class); + private static ScheduledThreadPoolExecutor logFlushTimer = ThreadPoolManager.newDaemonScheduledThreadPool(1, + "ranger-hive-audit-log-flusher-timer", true); private RangerHivePlugin hivePlugin; private RangerHiveAuditHandler auditHandler; @@ -47,12 +53,22 @@ 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 + logFlushTimer.scheduleAtFixedRate(new RangerHiveAuditLogFlusher(auditHandler), 10, 20L, TimeUnit.SECONDS); } private RangerAccessRequestImpl createRequest(UserIdentity currentUser, HiveAccessType accessType) { RangerAccessRequestImpl request = new RangerAccessRequestImpl(); - request.setUser(currentUser.getQualifiedUser()); - request.setUserRoles(currentUser.getRoles()); + // currentUser.getQualifiedUser() is as of form: default_cluster:user1, only use `user1` + String[] userArray = currentUser.getQualifiedUser().split(":"); + request.setUser(userArray[1]); + request.setClusterName(userArray[0]); + Set roles = new HashSet<>(); + for (String role : currentUser.getRoles()) { + // some roles are as of form: default_role_rbac_test@%, only use `default_role_rbac_test` + roles.add(role.split("@")[0]); + } + request.setUserRoles(roles); request.setAction(accessType.name()); if (accessType == HiveAccessType.USE) { request.setAccessType(RangerPolicyEngine.ANY_ACCESS); @@ -60,6 +76,7 @@ private RangerAccessRequestImpl createRequest(UserIdentity currentUser, HiveAcce request.setAccessType(accessType.name().toLowerCase()); } request.setClientIPAddress(currentUser.getHost()); + request.setClusterType(CLIENT_TYPE_DORIS); request.setClientType(CLIENT_TYPE_DORIS); request.setAccessTime(new Date()); @@ -68,28 +85,24 @@ private RangerAccessRequestImpl createRequest(UserIdentity currentUser, HiveAcce private void checkPrivileges(UserIdentity currentUser, HiveAccessType accessType, List hiveResources) throws AuthorizationException { - try { - List requests = new ArrayList<>(); - for (RangerHiveResource resource : hiveResources) { - RangerAccessRequestImpl request = createRequest(currentUser, accessType); - request.setResource(resource); + List requests = new ArrayList<>(); + for (RangerHiveResource resource : hiveResources) { + RangerAccessRequestImpl request = createRequest(currentUser, accessType); + request.setResource(resource); - requests.add(request); - } + requests.add(request); + } - Collection results = hivePlugin.isAccessAllowed(requests, auditHandler); - for (RangerAccessResult result : results) { - LOG.debug("match policy:" + result.getPolicyId()); - if (!result.getIsAllowed()) { - LOG.debug(result.getReason()); - throw new AuthorizationException(String.format( - "Permission denied: user [%s] does not have privilege for [%s] command on [%s]", - currentUser.getQualifiedUser(), accessType.name(), - result.getAccessRequest().getResource().getAsString())); - } + Collection results = hivePlugin.isAccessAllowed(requests, auditHandler); + for (RangerAccessResult result : results) { + LOG.debug(String.format("request %s match policy %s", result.getAccessRequest(), result.getPolicyId())); + if (!result.getIsAllowed()) { + LOG.debug(result.getReason()); + throw new AuthorizationException(String.format( + "Permission denied: user [%s] does not have privilege for [%s] command on [%s]", + result.getAccessRequest().getUser(), accessType.name(), + result.getAccessRequest().getResource().getAsString())); } - } finally { - auditHandler.flushAudit(); } } @@ -99,20 +112,20 @@ private boolean checkPrivilege(UserIdentity currentUser, HiveAccessType accessTy request.setResource(resource); RangerAccessResult result = hivePlugin.isAccessAllowed(request, auditHandler); - auditHandler.flushAudit(); if (result == null) { - LOG.warn(String.format("Error getting authorizer result, please check your ranger config. Request: %s", - request)); + LOG.warn(String.format("Error getting authorizer result, please check your ranger config. Make sure " + + "ranger policy engine is initialized. Request: %s", request)); return false; } if (result.getIsAllowed()) { + LOG.debug(String.format("request %s match policy %s", request, result.getPolicyId())); return true; } else { LOG.debug(String.format( "Permission denied: user [%s] does not have privilege for [%s] command on [%s]", - currentUser.getQualifiedUser(), accessType.name(), + result.getAccessRequest().getUser(), accessType.name(), result.getAccessRequest().getResource().getAsString())); return false; } @@ -123,7 +136,6 @@ public String getFilterExpr(UserIdentity currentUser, HiveAccessType accessType, RangerAccessRequestImpl request = createRequest(currentUser, accessType); request.setResource(resource); RangerAccessResult result = hivePlugin.isAccessAllowed(request, auditHandler); - auditHandler.flushAudit(); return result.getFilterExpr(); } @@ -133,7 +145,6 @@ public void getColumnMask(UserIdentity currentUser, HiveAccessType accessType, RangerAccessRequestImpl request = createRequest(currentUser, accessType); request.setResource(resource); RangerAccessResult result = hivePlugin.isAccessAllowed(request, auditHandler); - auditHandler.flushAudit(); LOG.debug(String.format("maskType: %s, maskTypeDef: %s, maskedValue: %s", result.getMaskType(), result.getMaskTypeDef(), result.getMaskedValue())); @@ -142,7 +153,9 @@ public void getColumnMask(UserIdentity currentUser, HiveAccessType accessType, public HiveAccessType convertToAccessType(PrivPredicate predicate) { if (predicate == PrivPredicate.SHOW) { return HiveAccessType.USE; - } else if (predicate == PrivPredicate.ADMIN) { + } else if (predicate == PrivPredicate.SELECT) { + return HiveAccessType.SELECT; + } else if (predicate == PrivPredicate.ADMIN || predicate == PrivPredicate.ALL) { return HiveAccessType.ALL; } else if (predicate == PrivPredicate.LOAD) { return HiveAccessType.UPDATE; @@ -152,27 +165,30 @@ public HiveAccessType convertToAccessType(PrivPredicate predicate) { return HiveAccessType.CREATE; } else if (predicate == PrivPredicate.DROP) { return HiveAccessType.DROP; - } else if (predicate == PrivPredicate.SELECT) { - return HiveAccessType.SELECT; } else { return HiveAccessType.NONE; } } + private String removeDefaultClusterName(String nameWithDefaultCluster) { + return nameWithDefaultCluster.startsWith("default_cluster:") + ? nameWithDefaultCluster.split(":")[1] : nameWithDefaultCluster; + } + @Override public boolean checkCtlPriv(UserIdentity currentUser, String ctl, PrivPredicate wanted) { - return false; + return true; } @Override public boolean checkDbPriv(UserIdentity currentUser, String ctl, String db, PrivPredicate wanted) { - RangerHiveResource resource = new RangerHiveResource(HiveObjectType.DATABASE, db); + RangerHiveResource resource = new RangerHiveResource(HiveObjectType.DATABASE, removeDefaultClusterName(db)); return checkPrivilege(currentUser, convertToAccessType(wanted), resource); } @Override public boolean checkTblPriv(UserIdentity currentUser, String ctl, String db, String tbl, PrivPredicate wanted) { - RangerHiveResource resource = new RangerHiveResource(HiveObjectType.TABLE, db, tbl); + RangerHiveResource resource = new RangerHiveResource(HiveObjectType.TABLE, removeDefaultClusterName(db), tbl); return checkPrivilege(currentUser, convertToAccessType(wanted), resource); } @@ -181,7 +197,8 @@ public void checkColsPriv(UserIdentity currentUser, String ctl, String db, Strin PrivPredicate wanted) throws AuthorizationException { List resources = new ArrayList<>(); for (String col : cols) { - RangerHiveResource resource = new RangerHiveResource(HiveObjectType.COLUMN, db, tbl, col); + RangerHiveResource resource = new RangerHiveResource(HiveObjectType.COLUMN, removeDefaultClusterName(db), + tbl, col); resources.add(resource); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/RangerHiveAuditLogFlusher.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/RangerHiveAuditLogFlusher.java new file mode 100644 index 00000000000000..50bf9cfa8aa88f --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/RangerHiveAuditLogFlusher.java @@ -0,0 +1,42 @@ +// 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.catalog.authorizer; + +import java.util.TimerTask; + +public class RangerHiveAuditLogFlusher extends TimerTask { + + private RangerHiveAuditHandler auditHandler; + + public RangerHiveAuditLogFlusher(RangerHiveAuditHandler auditHandler) { + this.auditHandler = auditHandler; + } + + @Override + public void run() { + while (true) { + this.auditHandler.flushAudit(); + + try { + Thread.sleep(20000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} From f6ca4b9ff3371b1dcb43e3586e0d6d9c52652ace Mon Sep 17 00:00:00 2001 From: Yulei-Yang Date: Sun, 5 Mar 2023 16:06:25 +0800 Subject: [PATCH 2/3] update according to review --- .../RangerHiveAccessController.java | 23 ++++++++++--------- .../apache/doris/mysql/privilege/Role.java | 4 ++++ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/RangerHiveAccessController.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/RangerHiveAccessController.java index 24ba399d5ffaee..6572794e2c29fd 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/RangerHiveAccessController.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/RangerHiveAccessController.java @@ -18,10 +18,12 @@ package org.apache.doris.catalog.authorizer; import org.apache.doris.analysis.UserIdentity; +import org.apache.doris.cluster.ClusterNamespace; import org.apache.doris.common.AuthorizationException; import org.apache.doris.common.ThreadPoolManager; import org.apache.doris.mysql.privilege.CatalogAccessController; import org.apache.doris.mysql.privilege.PrivPredicate; +import org.apache.doris.mysql.privilege.Role; import org.apache.hadoop.hive.ql.security.authorization.plugin.HiveAccessControlException; import org.apache.logging.log4j.LogManager; @@ -65,8 +67,10 @@ private RangerAccessRequestImpl createRequest(UserIdentity currentUser, HiveAcce request.setClusterName(userArray[0]); Set roles = new HashSet<>(); for (String role : currentUser.getRoles()) { - // some roles are as of form: default_role_rbac_test@%, only use `default_role_rbac_test` - roles.add(role.split("@")[0]); + // default role is as of form: default_role_rbac_xxx@%, not useful for Ranger + if (!Role.isDefaultRoleName(role)) { + roles.add(role); + } } request.setUserRoles(roles); request.setAction(accessType.name()); @@ -170,11 +174,6 @@ public HiveAccessType convertToAccessType(PrivPredicate predicate) { } } - private String removeDefaultClusterName(String nameWithDefaultCluster) { - return nameWithDefaultCluster.startsWith("default_cluster:") - ? nameWithDefaultCluster.split(":")[1] : nameWithDefaultCluster; - } - @Override public boolean checkCtlPriv(UserIdentity currentUser, String ctl, PrivPredicate wanted) { return true; @@ -182,13 +181,15 @@ public boolean checkCtlPriv(UserIdentity currentUser, String ctl, PrivPredicate @Override public boolean checkDbPriv(UserIdentity currentUser, String ctl, String db, PrivPredicate wanted) { - RangerHiveResource resource = new RangerHiveResource(HiveObjectType.DATABASE, removeDefaultClusterName(db)); + RangerHiveResource resource = new RangerHiveResource(HiveObjectType.DATABASE, + ClusterNamespace.getNameFromFullName(db)); return checkPrivilege(currentUser, convertToAccessType(wanted), resource); } @Override public boolean checkTblPriv(UserIdentity currentUser, String ctl, String db, String tbl, PrivPredicate wanted) { - RangerHiveResource resource = new RangerHiveResource(HiveObjectType.TABLE, removeDefaultClusterName(db), tbl); + RangerHiveResource resource = new RangerHiveResource(HiveObjectType.TABLE, + ClusterNamespace.getNameFromFullName(db), tbl); return checkPrivilege(currentUser, convertToAccessType(wanted), resource); } @@ -197,8 +198,8 @@ public void checkColsPriv(UserIdentity currentUser, String ctl, String db, Strin PrivPredicate wanted) throws AuthorizationException { List resources = new ArrayList<>(); for (String col : cols) { - RangerHiveResource resource = new RangerHiveResource(HiveObjectType.COLUMN, removeDefaultClusterName(db), - tbl, col); + RangerHiveResource resource = new RangerHiveResource(HiveObjectType.COLUMN, + ClusterNamespace.getNameFromFullName(db), tbl, col); resources.add(resource); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java index dc7b5a161b2560..8ae3ac5b792089 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java @@ -118,6 +118,10 @@ public Role(String roleName, TablePattern tablePattern, PrivBitSet tablePrivs, } + public static boolean isDefaultRoleName(String roleName) { + return roleName.startsWith(RoleManager.DEFAULT_ROLE_PREFIX); + } + public String getRoleName() { return roleName; } From 9ebd531a450df8665413085d5ce1dfdf6221fddb Mon Sep 17 00:00:00 2001 From: Yulei-Yang Date: Sun, 5 Mar 2023 16:23:44 +0800 Subject: [PATCH 3/3] update document --- docs/en/docs/lakehouse/multi-catalog/hive.md | 3 ++- docs/zh-CN/docs/lakehouse/multi-catalog/hive.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/en/docs/lakehouse/multi-catalog/hive.md b/docs/en/docs/lakehouse/multi-catalog/hive.md index 0692517cadc258..84154330457e56 100644 --- a/docs/en/docs/lakehouse/multi-catalog/hive.md +++ b/docs/en/docs/lakehouse/multi-catalog/hive.md @@ -131,7 +131,8 @@ when connecting to Hive Metastore which is authorized by Ranger, need some prope 2. update all FEs' runtime environment: a. copy all ranger-*.xml files to /conf which are located in HMS/conf directory b. update value of `ranger.plugin.hive.policy.cache.dir` in ranger--security.xml to a writable directory - c. restart FE + c. add a log4j.properties to /conf, thus you can get logs of ranger authorizer + d. restart FE diff --git a/docs/zh-CN/docs/lakehouse/multi-catalog/hive.md b/docs/zh-CN/docs/lakehouse/multi-catalog/hive.md index c168cfc57b1ea5..650f444dad0790 100644 --- a/docs/zh-CN/docs/lakehouse/multi-catalog/hive.md +++ b/docs/zh-CN/docs/lakehouse/multi-catalog/hive.md @@ -125,7 +125,8 @@ CREATE CATALOG hive PROPERTIES ( 2. 配置所有 FE 环境: a. 将 HMS conf 目录下的三个 Ranger 配置文件Copy到 /conf 目录下 b. 修改其中 ranger--security.xml 的属性 `ranger.plugin.hive.policy.cache.dir` 的值为一个可写目录 - c. 重启 FE + c. 为获取到 Ranger 鉴权本身的日志,可在 /conf 目录下添加配置文件 log4j.properties + d. 重启 FE