自定义Ranger插件 Zeppelin集成Ranger方案 (非LDAP模式)

序:

最近项目需求要用Ranger把Zeppelin管起来,之前聊过使用LDAP的模式,这次我们说说不使用LDAP,通过开发Ranger插件的方式将Zeppelin和Ranger继承起来

目录

序:

一、Zeppelin权限部分分析

1、zeppelin-note权限分析

2、zeppelin-shiro权限分析

二、shiro权限原理分析及Ranger原理分析

1、shiro认证授权原理

2、Ranger认证授权原理

三、自定义Ranger插件

1、编写shiroService

 2、编写资源json文件

 3、编写Ranger授权类

四、自定义Shiro拦截器(RangerFilter)

五、测试


一、Zeppelin权限部分分析

由官方文档可知,zeppelin权限是使用shiro接管的,认证、授权默认使用的shiro的textRealm,同时还支持LDAPRealm、PamRealm、JDBCRealm等其他Realm。

Zeppelin中的权限有两个大模块,一方面是note粒度的权限,另一方面是菜单权限粒度,解释器配置页面的访问权限、版本控制页面的访问权限以及其他一些页面url的拦截。

1、zeppelin-note权限分析

对于note粒度的权限,划分为可读可写可执行,配置的方式目前有两种:页面配置和RESTApi设置,如下图:

源码中实现过程是在org.apache.zeppelin.service.NotebookService类中,所有的note操作都要经过org.apache.zeppelin.service.NotebookService.checkPermission()方法来鉴权

  enum Permission {READER,WRITER,RUNNER,OWNER,}/*** Return null when it is allowed, otherwise return the error message which could be* propagated to frontend** @param noteId* @param context* @param permission* @param op* @return*/private  boolean checkPermission(String noteId,Permission permission,Message.OP op,ServiceContext context,ServiceCallback callback) throws IOException {boolean isAllowed = false;Set allowed = null;switch (permission) {case READER:isAllowed = authorizationService.isReader(noteId, context.getUserAndRoles());allowed = authorizationService.getReaders(noteId);break;case WRITER:isAllowed = authorizationService.isWriter(noteId, context.getUserAndRoles());allowed = authorizationService.getWriters(noteId);break;case RUNNER:isAllowed = authorizationService.isRunner(noteId, context.getUserAndRoles());allowed = authorizationService.getRunners(noteId);break;case OWNER:isAllowed = authorizationService.isOwner(noteId, context.getUserAndRoles());allowed = authorizationService.getOwners(noteId);break;}if (isAllowed) {return true;} else {String errorMsg = "Insufficient privileges to " + permission + " note.\n" +"Allowed users or roles: " + allowed + "\n" + "But the user " +context.getAutheInfo().getUser() + " belongs to: " + context.getUserAndRoles();callback.onFailure(new ForbiddenException(errorMsg), context);return false;}}

2、zeppelin-shiro权限分析

zeppelin默认使用shiro.ini的方式来配置权限,它还支持从LDAP和其他Realm同步用户  zeppelin集成LDAP https://blog.csdn.net/EdwardWong_/article/details/115401467 

我们在shiro中配置好的url,最终都会走org.apache.shiro.web.filter.authz.RolesAuthorizationFilter.isAccessAllowed()方法来处理,处理原理此处不细说,如果我们想使用Ranger来处理权限,此处是着手之处

package org.apache.shiro.web.filter.authz;import java.io.IOException;
import java.util.Set;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;// shiro 源码
public class RolesAuthorizationFilter extends AuthorizationFilter {public RolesAuthorizationFilter() {}public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {Subject subject = this.getSubject(request, response);String[] rolesArray = (String[])mappedValue;if (rolesArray != null && rolesArray.length != 0) {Set roles = CollectionUtils.asSet(rolesArray);return subject.hasAllRoles(roles);} else {return true;}}
}

二、shiro权限原理分析及Ranger原理分析

1、shiro认证授权原理

// Todo...

2、Ranger认证授权原理

// Todo...

对于note粒度的权限,如果我们使用Ranger来鉴权,对zeppelin源码入侵性过大,后续zeppelin官方的升级适配和其他一些问题过于繁琐,所以再三思考过后决定先实现菜单权限层面的Ranger管控

三、自定义Ranger插件

官方给出了一些案例,阅读完后感觉一言难尽,了解到一些大概的流程,但是对于开发自定义插件帮助实在有限,于是一边借鉴其他官方Ranger插件的代码,一遍捋Ranger源码,最后终于开发成功。

ranger插件包层次如下

1、编写shiroService

pom文件如下:


rangerorg.apache.ranger2.0.04.0.0ranger-shiro-pluginorg.apache.shiroshiro-core1.7.0org.apache.shiroshiro-web1.7.0org.apache.shiroshiro-config-core1.7.0org.apache.rangerranger-plugins-common2.0.0org.apache.rangerranger-plugins-audit2.0.0org.apache.rangercredentialbuilder2.0.0org.apache.maven.pluginsmaven-shade-plugin2.3packageshade

首先编写RangerService类,需要继承RangerBaseService类,并实现init()、validateConfig()、lookupResource方法。validateConfig()方法是对参数的验证方法,会对在RangerWeb界面上配置的zeppelin参数进行认证。lookResource方法是RangerWeb的一个自动补全参数方法,代码如下

package org.apache.ranger.services.shiro;import org.apache.ranger.plugin.client.BaseClient;
import org.apache.ranger.plugin.model.RangerService;
import org.apache.ranger.plugin.model.RangerServiceDef;
import org.apache.ranger.plugin.service.RangerBaseService;
import org.apache.ranger.plugin.service.ResourceLookupContext;import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @author Edwards Wang* @version 2021/4/13 下午5:39* describe:*/
public class RangerServiceShiroTest extends RangerBaseService {@Overridepublic void init(RangerServiceDef serviceDef, RangerService service) {super.init(serviceDef, service);}@Overridepublic Map validateConfig() throws Exception {String errMes = "Failed,cannot connext to the zeppelin Service";String successMes = "Connection Success";Map map = new HashMap<>();if (!configs.containsKey("interpreterPage")){BaseClient.generateResponseDataMap(false,errMes,errMes,null,null,map);}else {BaseClient.generateResponseDataMap(true,successMes,successMes,null,null,map);}return map;}@Overridepublic List lookupResource(ResourceLookupContext context) throws Exception {return null;}}

 2、编写资源json文件

我们要对我们的插件有一个描述文件,文件要放在agents-common/src/main/resources/service-defs/路径下

首先是对插件的描述,需要有id、name、类路径、标签和描述

  "id":29,"name": "shiroTest2","implClass": "org.apache.ranger.services.shiro.RangerServiceShiroTest","label": "SHIRO Repository","description": "SHIRO Repository","resources":

 然后是对我们插件要管控的资源的描述

{"itemId": 1,"name": "path","type": "path","level": 10,"parent": "","mandatory": true,"lookupSupported": true,"recursiveSupported": true,"excludesSupported": false,"matcher": "org.apache.ranger.plugin.resourcematcher.RangerPathResourceMatcher","matcherOptions": { "wildCard":true, "ignoreCase":false },"validationRegEx":"","validationMessage": "","uiHint":"","label": "url","description": "url"}

然后是对权限的设置,这里只开启了一个权限get(此get和http请求无关,只是是否可以访问) 

   {"itemId": 1,"name": "get","label": "get"}

完整的文件内容如下

{"id":29,"name": "shiroTest2","implClass": "org.apache.ranger.services.shiro.RangerServiceShiroTest","label": "SHIRO Repository","description": "SHIRO Repository","resources":[{"itemId": 1,"name": "path","type": "path","level": 10,"parent": "","mandatory": true,"lookupSupported": true,"recursiveSupported": true,"excludesSupported": false,"matcher": "org.apache.ranger.plugin.resourcematcher.RangerPathResourceMatcher","matcherOptions": { "wildCard":true, "ignoreCase":false },"validationRegEx":"","validationMessage": "","uiHint":"","label": "url","description": "url"}],"accessTypes":[{"itemId": 1,"name": "get","label": "get"}],"configs":[{"itemId": 1,"name": "interpreterPage","type": "string","subType": "","mandatory": true,"validationRegEx":"","validationMessage": "","uiHint":"","label": "interpreterPage"}]
}

 在完成service和资源文件后,需要执行命令将我们的插件加载进Ranger,命令如下:

curl -u admin:admin -X POST -H "Accept: application/json" -H "Content-Type: application/json" –d @ranger-servicedef-shiro.json 'http://localhost:6080/service/public/v2/api/servicedef'

然后需要重启RangerAdmin,接下来打开RangerWeb界面就能看到我们的插件了

 3、编写Ranger授权类

我们的RangerShiroAuthorizer要继承shiro的org.apache.shiro.web.filter.authz.AuthorizationFilter类,并实现isAccessAllowed方法

package org.apache.ranger.authorization.shiro.authorizer;import org.apache.ranger.plugin.audit.RangerDefaultAuditHandler;
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.service.RangerBasePlugin;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;/*** @author Edwards Wang* @version 2021/4/13 下午2:16* describe:*/
public class RangerShiroAuthorizer extends AuthorizationFilter {public RangerShiroAuthorizer() {}@Overridepublic boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {Subject subject = getSubject(request,response);// 获取用户String userName = (String) subject.getPrincipal();HttpServletRequest httpServletRequest = (HttpServletRequest) request;// 获取要拦截的urlStringBuffer url = httpServletRequest.getRequestURL();RangerAccessResourceImpl resource = new RangerAccessResourceImpl();resource.setValue("path",url.toString());RangerAccessRequestImpl rangerAccessRequest = new RangerAccessRequestImpl(resource,"get",userName,null);// 此处的名字要和json文件中定义的name插件名一致RangerBasePlugin rangerBasePlugin = new RangerBasePlugin("shiroTest2", "shiroTest2");rangerBasePlugin.setResultProcessor(new RangerDefaultAuditHandler());rangerBasePlugin.init();// 将权限交由Ranger来审核,返回boolean的值再交给shiroRangerAccessResult result = rangerBasePlugin.isAccessAllowed(rangerAccessRequest);return result != null && result.getIsAllowed();}
}

4、编写audit文件和security文件,我们要将这两个文件放在插件包的resource路径下:

首先是ranger-shiroTest2-audit.xml




xasecure.audit.destination.dbfalsexasecure.audit.destination.db.jdbc.drivercom.mysql.jdbc.Driverxasecure.audit.destination.db.jdbc.urljdbc:mysql://localhost/ranger_auditxasecure.audit.destination.db.passwordrangerloggerxasecure.audit.destination.db.userrangerloggerxasecure.audit.destination.db.batch.filespool.dir/tmp/audit/db/spoolxasecure.audit.destination.hdfsfalsexasecure.audit.destination.hdfs.dirhdfs://localhost:8020/ranger/auditxasecure.audit.destination.hdfs.batch.filespool.dir/tmp/audit/hdfs/spoolxasecure.audit.destination.log4jtruexasecure.audit.destination.log4j.loggerranger_audit_logger

 然后是ranger-shiroTest2-security.xml

有一个属性,需要特别注意。 这就是名为ranger.plugin.httpservice.service.name的属性。 此属性的值必须与您在Ranger UI中使用的服务名称相同。




ranger.plugin.shiroTest2.policy.rest.urlhttp://192.168.101.48:6080URL to Ranger Adminranger.plugin.shiroTest2.service.nameMyServiceName of the Ranger service containing policies for this shiroTest2 instanceranger.plugin.shiroTest2.policy.source.implorg.apache.ranger.admin.client.RangerAdminRESTClientClass to retrieve policies from the sourceranger.plugin.shiroTest2.policy.rest.ssl.config.fileranger-policymgr-ssl.xmlPath to the file containing SSL details to contact Ranger Adminranger.plugin.shiroTest2.policy.pollIntervalMs30000How often to poll for changes in policies?ranger.plugin.shiroTest2.policy.cache.dir/tmpDirectory where Ranger policies are cached after successful retrieval from the sourceranger.plugin.shiroTest2.policy.rest.client.connection.timeoutMs120000RangerRestClient Connection Timeout in Milli Secondsranger.plugin.shiroTest2.policy.rest.client.read.timeoutMs30000RangerRestClient read Timeout in Milli Seconds

然后打包。接下来开始写shiro拦截器部分 

四、自定义Shiro拦截器(RangerFilter)

在zeppelin的zeppelin-server/src/main/java/org/apache/zeppelin下新建一个filter包,在filter包下新建一个shiro拦截器:RangerFilter,继承org.apache.shiro.web.filter.authz.AuthorizationFilter方法,实现isAccessAllowed方法,在方法里直接调用我们ranger插件包内的isAccessAllowed方法

package org.apache.zeppelin.filter;import org.apache.ranger.authorization.shiro.authorizer.RangerShiroAuthorizer;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;/*** @author Edwards Wang* @version 2021/4/20 下午2:30* describe:*/
public class RangerFilter extends AuthorizationFilter {@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {return new RangerShiroAuthorizer().isAccessAllowed(request,response,mappedValue);}
}

然后修改shiro.ini文件,在[main]模块下新增条目

[main]
rangerFilter = org.apache.zeppelin.filter.RangerFilter

在[url]模块下修改条目,此处只以解释器页面为例

[urls]
/api/version = anon
/api/cluster/address = anon
/api/interpreter/setting/restart/** = authc
/api/interpreter/** = authc, rangerFilter

 完整的shiro.ini文件如下

#
# 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.
#[users]
# List of users with their password allowed to access Zeppelin.
# To use a different strategy (LDAP / Database / ...) check the shiro doc at http://shiro.apache.org/configuration.html#Configuration-INISections
# To enable admin user, uncomment the following line and set an appropriate password.
#admin = password1, admin
user1 = password2, role1, role2
user2 = password3, role3
user3 = password4, role2# Sample LDAP configuration, for user Authentication, currently tested for single Realm
[main]
### A sample for configuring Active Directory Realm
#activeDirectoryRealm = org.apache.zeppelin.realm.ActiveDirectoryGroupRealm
#activeDirectoryRealm.systemUsername = userNameA#use either systemPassword or hadoopSecurityCredentialPath, more details in http://zeppelin.apache.org/docs/latest/security/shiroauthentication.html
#activeDirectoryRealm.systemPassword = passwordA
#activeDirectoryRealm.hadoopSecurityCredentialPath = jceks://file/user/zeppelin/zeppelin.jceks
#activeDirectoryRealm.searchBase = CN=Users,DC=SOME_GROUP,DC=COMPANY,DC=COM
#activeDirectoryRealm.url = ldap://ldap.test.com:389
#activeDirectoryRealm.groupRolesMap = "CN=admin,OU=groups,DC=SOME_GROUP,DC=COMPANY,DC=COM":"admin","CN=finance,OU=groups,DC=SOME_GROUP,DC=COMPANY,DC=COM":"finance","CN=hr,OU=groups,DC=SOME_GROUP,DC=COMPANY,DC=COM":"hr"
#activeDirectoryRealm.authorizationCachingEnabled = false### A sample for configuring LDAP Directory Realm
#ldapRealm = org.apache.zeppelin.realm.LdapGroupRealm
## search base for ldap groups (only relevant for LdapGroupRealm):
#ldapRealm.contextFactory.environment[ldap.searchBase] = dc=COMPANY,dc=COM
#ldapRealm.contextFactory.url = ldap://ldap.test.com:389
#ldapRealm.userDnTemplate = uid={0},ou=Users,dc=COMPANY,dc=COM
#ldapRealm.contextFactory.authenticationMechanism = simple### A sample PAM configuration
#pamRealm=org.apache.zeppelin.realm.PamRealm
#pamRealm.service=sshd### A sample for configuring ZeppelinHub Realm
#zeppelinHubRealm = org.apache.zeppelin.realm.ZeppelinHubRealm
## Url of ZeppelinHub
#zeppelinHubRealm.zeppelinhubUrl = https://www.zeppelinhub.com
#securityManager.realms = $zeppelinHubRealm## A same for configuring Knox SSO Realm
#knoxJwtRealm = org.apache.zeppelin.realm.jwt.KnoxJwtRealm
#knoxJwtRealm.providerUrl = https://domain.example.com/
#knoxJwtRealm.login = gateway/knoxsso/knoxauth/login.html
#knoxJwtRealm.logout = gateway/knoxssout/api/v1/webssout
#knoxJwtRealm.logoutAPI = true
#knoxJwtRealm.redirectParam = originalUrl
#knoxJwtRealm.cookieName = hadoop-jwt
#knoxJwtRealm.publicKeyPath = /etc/zeppelin/conf/knox-sso.pem
#
#knoxJwtRealm.groupPrincipalMapping = group.principal.mapping
#knoxJwtRealm.principalMapping = principal.mapping
#authc = org.apache.zeppelin.realm.jwt.KnoxAuthenticationFilter
rangerFilter = org.apache.zeppelin.filter.RangerFiltersessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager### If caching of user is required then uncomment below lines
#cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
#securityManager.cacheManager = $cacheManager### Enables 'HttpOnly' flag in Zeppelin cookies
cookie = org.apache.shiro.web.servlet.SimpleCookie
cookie.name = JSESSIONID
cookie.httpOnly = true
### Uncomment the below line only when Zeppelin is running over HTTPS
#cookie.secure = true
sessionManager.sessionIdCookie = $cookiesecurityManager.sessionManager = $sessionManager
# 86,400,000 milliseconds = 24 hour
securityManager.sessionManager.globalSessionTimeout = 86400000
shiro.loginUrl = /api/login[roles]
role1 = *
role2 = *
role3 = *
admin = *[urls]
# This section is used for url-based security. For details see the shiro.ini documentation.
#
# You can secure interpreter, configuration and credential information by urls.
# Comment or uncomment the below urls that you want to hide:
# anon means the access is anonymous.
# authc means form based auth Security.
#
# IMPORTANT: Order matters: URL path expressions are evaluated against an incoming request
# in the order they are defined and the FIRST MATCH WINS.
#
# To allow anonymous access to all but the stated urls,
# uncomment the line second last line (/** = anon) and comment the last line (/** = authc)
#
/api/version = anon
/api/cluster/address = anon
# Allow all authenticated users to restart interpreters on a notebook page.
# Comment out the following line if you would like to authorize only admin users to restart interpreters.
/api/interpreter/setting/restart/** = authc
/api/interpreter/** = authc, rangerFilter
/api/notebook-repositories/** = authc, roles[admin]
/api/configurations/** = authc, roles[admin]
/api/credential/** = authc, roles[admin]
/api/admin/** = authc, roles[admin]
#/** = anon
/** = authc

 修改完毕,将ranger和ranger插件的包放到lib路径下重启zeppelin

五、测试

首先打开zeppelin的界面,使用默认的user1用户登录,访问interpreter界面,会发现报错说没有权限

然后我们进入RangerWeb界面配置权限,首先配置插件

然后配置策略,将url和用户角色权限加上

点击add或save,然后重新回到zeppelin界面,这回就可以正常访问解释器页面了

大功告成!!

有任何问题可以评论留言,开发过程中有一些坑,大家遇到我踩过的可以帮大家解答一下


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部