如果您在这里,您已经知道什么是Apache Ranger 。 它是管理Hadoop框架中安全性的最流行(即使不是唯一)的方法。 它与Active Directory,Kerberos和其他各种身份验证集成在一起,但是我认为最有趣的功能是其授权支持。 作为Hadoop生态系统的一部分,人们会对它对Hadoop生态系统中的大多数框架(Hive,HBase,HDFS等)具有内建的支持(通过插件)感到惊讶,但是,我发现旋转它实际上非常容易自己的游侠自定义插件。
这篇文章将重点介绍Ranger插件中设计的简单性,并展示为自己构建一个插件有多么容易。 作为示例,我们将构建一个Ranger插件,用于管理对使用Akka HTTP编写的简单HTTP服务的访问。
Note : You are not required to know about Akka HTTP to follow this post. All you needed to know is that Akka HTTP is just a way (albeit, a great way) to build HTTP services
这篇文章后面的代码分为两个存储库:
- Ranger HTTP插件
- 护林员托管的Akka HTTP服务
写一个插件
为了重申我们在这里试图做的事情,我们将编写一个REST服务,并让Ranger管理它的授权。
编写Ranger插件实际上是两部分的问题–编写服务器端组件和应用程序端组件 。
- 服务器端组件是驻留在Ranger端的代码/配置。
- 应用程序端组件是驻留在我们的REST服务中的代码,该代码调用Ranger服务并检查应用程序的最终用户是否有权访问他所请求的资源。
我们将详细研究这两件事。 让我们尝试首先编写服务器端组件。
1.服务器端组件:
作为启发,如果我们打开Ranger代码库 ,我们可以看到一些内置插件。
如图所示,在Ranger代码库中,我们有许多插件,我们想添加自己的插件。
放大上图,插件上的服务器端组件将意味着编写一个
- servicedef配置
- 继承
RangerBaseService
的类
因此,实际上需要在服务器端实现“一个”配置和“一个”类。
1. SERVICEDEF配置
让我们看一下Hive的servicedef配置:
我认为,我们在这里谈论三件事:
A.资源:
在Hive示例中,对于Kafka,我们要保护的“资源”是数据库 , 表和列 ,对于HDFS,我们要保护的“资源”是Kafka 主题 ,它将是文件路径 。 对于我们的HTTP服务,我们试图保护的资源是REST slug 。 我们称之为“路径”。
"resources": [{"itemId": 1,"name": "path","type": "path","level": 10,"parent": "","mandatory": true,"lookupSupported": true,"recursiveSupported": true,"excludesSupported": true,"matcher": "org.apache.ranger.plugin.resourcematcher.RangerPathResourceMatcher","matcherOptions": {"wildCard": true,"ignoreCase": true},"validationRegEx": "","validationMessage": "","uiHint": "","label": "HTTP Path","description": "HTTP Path"}
访问类型:
访问类型只是意味着用户需要的访问类型–例如,对于Hive来说, select , create , delete就是示例。 对于HDFS, read , write , execute是示例。 对于Kafka, 发布并使用 。 对于我们的HTTP服务,访问类型将为HTTP方法– GET , POST , DELETE 。
"accessTypes": [{"itemId": 1,"name": "get","label": "get"},{"itemId": 2,"name": "post","label": "post"},{"itemId": 3,"name": "delete","label": "delete"}]
C.配置:
我们知道Ranger可以管理多个Kakfa主题,HDFS和HBase群集的安全性。 每个服务都将在不同的主机中运行,并且对每个服务进行身份验证的方式将有所不同。 捕获此信息的地方将是此configs
部分。 为了简化本示例,我们不关心HTTP服务的身份验证。 因此,我们只是捕获了可以ping通的URL,以确保我们的服务已启动并正在运行。
"configs": [{"itemId": 1,"name": "services_list_url","type": "string","subType": "","mandatory": true,"validationRegEx": "","validationMessage": "","uiHint": "","label": "HTTP URL for the services list eg. http://localhost:8080/services"}]
2.继承RANGERBASESERVICE的类
为RangerBaseService
插件实现服务器端组件的第二部分和最后一部分是编写一个继承RangerBaseService
的类。
该类希望重写两个函数:
-
validateConfig
:请记住servicedef的configs
部分。 显然,我们将接受这些参数的值,对吗? 现在,这个validateConfig
是我们验证传递的值的地方。 对于我们的HTTP服务,我们在配置中接受的只是services_list_url 。 现在,该功能的实现将是使用一个简单的HTTP客户端ping并检查服务是否已启动并正在运行。
class RangerServiceHTTP extends RangerBaseService {override def validateConfig(): util.Map[String, AnyRef] = {if (configs.containsKey("services_list_url")) {val serviceUp = HttpServiceClient.isServiceUp(configs.get("services_list_url"))if (serviceUp) retSuccessMap() else returnFailureMap()}else {returnFailureMap()}}
-
lookupResource
:这是一个有趣的功能。 考虑以下屏幕截图。
稍后,当我们配置访问策略时,我们将在其中配置资源 。 现在,此功能用于查找和自动填充这些资源。 假设,如果我们要输入HDFS资源或Hive表,那么选项的数量就很多,而且很容易打错字。 对于Hive,此功能将连接到metastore并为我们填充表和数据库。
对于HTTP服务,请记住service_list_url
? 该URL将仅返回逗号分隔的REST资源列表。 为了实现此功能,我只是再次调用服务并标记响应。
override def lookupResource(resourceLookupContext: ResourceLookupContext): util.List[String] = {val serviceUrl = configs.get("services_list_url")HttpServiceClient.getServicePaths(serviceUrl).asJava}
现在,作为代码的最后一步,我们需要将RangerServiceHTTP
这个类和servicedef配置联系在一起。 我们这样做的方法是通过在implClass
属性中配置类。 还要注意,我们正在将该Ranger插件的名称配置为httpservice
:
{"name": "httpservice","label": "HTTP Service","description": "Rudimentary Ranger plugin to enforce security on top of a HTTP Service","guid": "b8290b7f-6f69-44a9-89cc-06b6975ea676","implClass": "com.arunma.ranger.http.RangerServiceHTTP",
* * "version": 1,"isEnabled": 1,"resources": [{"itemId": 1,"name": "path",......
完整的配置如下所示 。
还有两个较小的管理步骤:
- 为了确保我们的类在Ranger类路径上可用,我们将其捆绑到一个jar中,并将其放在
<RANGER_HOME>/ews/webapp/WEB-INF/classes/ranger-plugins/httpservice
。 文件夹httpservice
的名称与servicedef
配置中声明的名称相对应。
- 将我们的配置上传到Ranger中,以便我们的服务在Ranger UI中可见。
curl -u admin:admin -X POST -H "Accept: application/json" -H "Content-Type: application/json" --data @http-ranger.json http://localhost:6080/service/plugins/definitions
重新启动Ranger服务器。
耶! 现在,我们在Ranger UI上看到HTTPSERVICE
2.应用程序侧组件:
在应用程序方面,事情再简单不过了。 为了使用Ranger中使用的策略,应用程序需要做的就是调用Ranger并检查用户是否有权访问资源。 该函数从字面上称为isAccessAllowed
。
以下代码几乎是需要在应用程序端编写的所有代码:
package com.arunma.rangerimport org.apache.ranger.plugin.audit.RangerDefaultAuditHandler
import org.apache.ranger.plugin.policyengine.{RangerAccessRequestImpl, RangerAccessResourceImpl}
import org.apache.ranger.plugin.service.RangerBasePluginimport scala.collection.JavaConverters._object RangerAuthorizer {lazy val plugin = {val plg = new RangerBasePlugin("httpservice", "httpservice")plg.setResultProcessor(new RangerDefaultAuditHandler)plg.init()plg}def authorize(path: String, accessType: String, userName: String, userGroups: Set[String] = Set("public")): Boolean = {val resource = new RangerAccessResourceImpl()resource.setValue("path", path)val request = new RangerAccessRequestImpl(resource, accessType, userName, userGroups.asJava)val result = plugin.isAccessAllowed(request)result != null && result.getIsAllowed}
}
RangerBasePlugin("httpservice", "httpservice")
和init()
函数用作我们进入Ranger服务的入口。 注意RangerBasePlugin
的httpservice
参数。 该名称必须与servicedef配置中提供的名称匹配。
authorize
函数是拦截器在客户端被授予对REST资源的访问权之前调用的函数。 该函数只是构造一个AccessRequest – RangerAccessRequestImpl
并调用插件的isAccessAllowed
函数,该函数返回Boolean
。
拦截器指令 authorize
调用isRangerAuthorized
函数,然后在RangerAuthorizer中调用authorize
函数。
def isRangerAuthorized(path: String, httpMethod: String, userName: String): Boolean = RangerAuthorizer.authorize(path, httpMethod.toLowerCase, userName) lazy val userRoutes: Route =headerValueByName("username") { userName =>extractMethod { method =>pathPrefix("users") {extractMatchedPath { matchedPath =>authorize(isRangerAuthorized(matchedPath.toString(), method.name(), userName)) {concat(pathEnd {concat(get {val users: Future[Users] =(userRegistryActor ? GetUsers).mapTo[Users]complete(users)
我们需要做的最后一件事是将audit
和security
xml复制到我们的类路径中。 这些就像Ranger的站点xmls 。 对于本练习,我们将xmls放置在resources
目录中。
audit
xml和security
xml可以从游侠代码库复制。 如果您正在运行本地管理员,则审核XML可以保持原样,但是需要为我们的服务更改security
xml。 实现此目的的最简单方法是从护林员代码库中复制示例xml,然后开始将服务替换为httpservice
如下所示:
还有一个属性,需要特别注意。 这就是名为ranger.plugin.httpservice.service.name
的属性。 此属性的值必须与您在Ranger UI中使用的服务名称相同。
<property><name>ranger.plugin.httpservice.service.name</name><value>MyService</value><description>Name of the Ranger service containing policies for this httpservice instance</description>
</property>
试乘
这将涉及两个步骤
- 配置Ranger策略
- 验证您的HTTP服务
1.配置范围政策
2.验证您的HTTP服务
让我们通过启动HTTP服务来验证策略-启动com.arunma.RangerManagedHttpServer
策略配置的用户
curl -X GET -H 'username:arunma' http://localhost:8080/users
无效的用户
curl -X GET -H 'username:nobody' http://localhost:8080/users
摘要
Ranger插件有两个部分–服务器端组件和客户端组件。 对于服务器端组件,我们创建了一个servicedeef
json和一个继承了RangerBaseService
的类。 对于客户端组件,我们只调用了plugin
的isAccessAllowed
函数。
您现在可以使用Ranger授权的HTTP服务。
谢谢阅读。 快乐黑客!
翻译自: https://www.javacodegeeks.com/2019/05/beautiful-simplicity-apache-ranger-plugin.html