要在ranger中支持一个新的服务模块的权限校验,可以分为两部分,一部分是在ranger中添加一个服务模块,然后添加该服务的实例并配置对应的权限策略;另一部分就是在真正的服务端开发插件,从ranger中拉取权限策略,并提供接口完成真正的鉴权动作。
本文就来聊一聊相关的知识。
在ranger中添加服务模块
在ranger中添加服务模块可以分为3个步骤:
- 编写服务定义配置文件
具体怎么编写这个配置文件,可以参考上篇文章,里面详细介绍了配置文件中的各个字段。
配置文件通常命名为"ranger-servicedef-xxx.json",其中xxx为服务的名字,即和配置文件中的name字段的值保持一致。
另外,该文件需存放到agents-common/src/main/resources/service-defs目录下。
- 编写ranger中的服务实现类
还记得在服务定义配置文件中有个字段为implClass吗,它的值为类的名称。
例如:org.apache.ranger.services.rabbitmq.RangerServiceRabbitmq
每个服务模块都需要有一个对应的实现类。这个实现类通常继承自抽象类RangerBaseService,在构造函数和init方法中,一般都是调用父类的实现。
而两个需要自行实现的方法为:
// 实现对配置文件d的有效性j检查
public abstract Map<String, Object> validateConfig() throws Exception;
// 实现对资源的检索
public abstract List<String> lookupResource(ResourceLookupContext context) throws Exception;
可以根据实际需要完成具体的实现。当然,也可以返回一个空的map或list,即不进行有效性检查和不支持检索。
另外,还有一个可以重写的方法是:
public List<RangerPolicy> getDefaultRangerPolicies() throws Exception
该方法在父类实现中提供了默认的策略。即一旦在ranger的web界面中添加了该服务的一个实例,那么会自动添加默认的策略。
因此,可以在具体实现类中根据需要改写该方法的实现。
- 加载服务模块
完成前面两步后,在ranger中添加服务的主要工作基本完成,剩下的就是启动初始化加载该服务模块(实例化具体的类对象)使其可以在界面中展示了。
初始化动作是在EmbeddedServiceDefsUtil类中完成的,具体包括:
-
在静态变量DEFAULT_BOOTSTRAP_SERVICEDEF_LIST中添加服务名
注意:名称要和服务定义配置文件中name的值完全匹配。
- 添加类成员,并在init方法中完成初始实例化动作。
简单示例代码如下:
public static final string RABBITMQ_IMPL_CLASS_NAME = "rabbitmq";
private RangerServiceDef rabbitmqServiceDef;
public void init(ServiceStore store) {
...
rabbitmqServiceDef = getOrCreateServiceDef(store, RABBITMQ_IMPL_CLASS_NAME);
}
完成上述步骤后,重新编译ranger,然后安装部署启动ranger,在ranger的web界面,就可以看到我们添加的服务模块了。
除了上面的方法,在官网的rest接口文档中,看到还可以通过rest接口添加服务模块。
实测了一下,确实可以成功添加,在ranger的web控制台上也能看到添加的服务模块。但是有一点需要注意:由于服务定义配置文件中需要指明对应的实现类,而如果ranger内部找不到对应的实现类,虽然可以成功添加服务模块,但是在添加服务模块的具体实例时,会因为没有对应的实现类而报错!
一种可行的解决办法是:
仍旧需要完成对应类的实现,并制作相应的jar包,然后将jar包放到ranger服务对应的插件目录中,如下图所示:
此后,再通过rest接口添加服务模块,就不会报错了。
插件开发
完成了在ranger中添加模块后,接下来的工作就是编写插件,并嵌入到具体服务中,完成具体的鉴权动作了。
实际上,ranger已经提供了完备的框架,我们只需要简单开发两三个继承类,最终调用指定接口就可以完成整个插件开发了。
在进行开发之前,有必要先了解ranger插件框架中几个核心的类。
-
RangerBasePlugin
ranger插件中最核心的类,负责从ranger拉取策略并缓存,同时对外暴露接口完成资源访问的权限校验。
-
RangerAccessResourceImpl
资源的封装类,调用鉴权接口时,需要构造这么一个类,并设置需要鉴权的资源。
-
RangerAccessRequestImpl
资源访问的封装类,内部包含了上面提到的资源封装类,同时还包括访问资源的用户、用户组、角色、访问的类型、客户端IP等信息。最终调用接口进行权限校验时,该类的实例对象需要作为参数被传入。
-
RangerDefaultAuditHandler
审计日志的处理类,所有的权限校验动作都可以作为审计日志被记录下来
有了上面的大概认识后,对于插件开发,我们只需要做这么几个动作:
- 编写一个继承RangerBasePlugin的类
通常,只需要实现构造函数和init方法即可。
在构造函数中,会调用父类的构造函数,这里需要正确传入服务的类型,也就是服务定义配置文件中name字段的值。
另外就是,在init方法中,调用父类的init方法,这个时候在插件内部会触发启动线程,向ranger注册并定时从ranger拉取策略。
- 编写粘合类
这是一个承上启下的类,也就是在具体服务内部先调用该类完成初始化动作,此后调用该类的接口进行权限校验。
而在这个类的内部,将上面RangerBasePlugin的继承类对象作为类成员,初始化时调用RangerBasePlugin继承类对象的init方法,完成插件的初始化动作。
而对外提供的的鉴权接口中,则对需要权限校验的资源、访问动作、访问的用户、用户组等信息封装成RangerAccessRequestImpl类对象,最后调用RangerBasePlugin继承类对象的isAccessAllowed方法,完成权限校验动作。
- 编写一个继承RangerDefaultAuditHandler的类(可选)
一个简单的示例代码:
public class RangerRabbitmqAuthorizer {
private static volatile RangerRabbitmqPlugin rmqPlugin = null;
public RangerRabbitmqAuthorizer () {}
public void init() {
RangerRabbitmqPlugin plugin = rmqPlugin;
if(plugin == null) {
synchronized(RangerRabbitmqAuthorizer.class) {
plugin = rmqPlugin;
if(plugin==null) {
plugin = new RangerRabbitmqPlugin();
plugin.init();
rmqPlugin = plugin;
}
}
}
}
public boolean checkPermission(String userName, String userGroups, string clientIp, String a) {
Date eventTime = new Date();
RangerAccessRequestImpl rangerRequest = new RangerAccessRequestImpl();
rangerRequest.setUser(userName);
rangerRequest.setUserGroups(Sets.newHashSet(userGroups));
rangerRequest.setClientIPAddress(clientIp);
rangerRequest.setAccessTime(eventTime);
String action = accessType;
RangerAccessResourceImpl rangerResource = new RangerAccessResourceImpl();
rangerRequest.setResource(rangerResource);
rangerRequest.setAccessType(accessType);
rangerRequest.setAction(action);
rangerRequest.setRequestData(resourceName);
rangerResource.setValue(resourceType, resourceName);
boolean retValue = false;
try {
RangerAccessResult result = rmqPlugin.isAccessAllowed(rangerRequest);
if(result == null) {
// Ranger plugin return null, returning false
} else {
retValue = result.getIsAllowed();
}
} catch (Throwable t) {
} finally {
}
return retValue;
}
public static void main(String[] args) {
RangerRabbitmqAuthorizer rmqAuthorizer = new RangerRabbitmqAuthorizer();
rmqAuthorizer.init();
Boolean isAllowed = rmqAuthorizer.checkPermission(
"hncscwc", // 资源访问的用户名
"hncscwc", // 资源访问的用户组
"127.0.0.1", // 资源访问的客户端IP
"consume", // 资源访问的类型(对队列j进行消费)
"queue", // 资源访问的类型
"test" // 具体访问的队列名称
}
}
class RangerRabbitmqPlugin extends RangerBasePlugin {
public RangerRabbitmqPlugin() {
super("rabbitmq", "rabbitmq");
}
public void init() {
super.init();
RangerDefaultAuditHandler auditHandler = new RangerDefaultAuditHandler(getConfig());
}
super.setResultProcessor(auditHandler);
}
}
来小结一下:
本文主要介绍了在ranger中支持一个新服务(插件开发)权限校验的步骤和流程,最后也给出了简单示例代码。未提到的一部分是整个工程的设置、编译打包的一些细节,还包括插件端配置文件的一些内容,这一块相对比较简单,也可以直接参考源码中其他模块的实现。
本文转载自 hncscwc,原文链接:https://www.modb.pro/db/131554。