Apache Ranger对HDFS的访问权限控制的原理分析(一)
介绍
Aapche Ranger是以插件的形式集成到HDFS中,由Ranger Admin管理访问策略,Ranger插件定期轮询Admin更新策略到本地,并根据策略信息进行用户访问权限的判定。其中提供管理员管理策略、插件的Ranger web和Ranger Plugin,与Admin之间的通信是基于HTTP的RESTful架构。Ranger集成HDFS的架构图如下:
Ranger对HDFS访问控制的实现原理
HDFS本身是有访问控制机制的,即在身份认证机制之后通过查询ACLs来对用户的权限检查,该权限检查的实现代码是INodeAttributeProvider抽象类中接口AccessControlEnforcer的checkPermission方法。Ranger将HDFS NameNode 的Inode attribute的提供类即INodeAttributeProvider抽象类修改为Ranger自己写的类,该类继承了INodeAttributeProvider抽象类。即在hdfs-site.xml文件中修改如下配置项。
<name>dfs.namenode.inode.attributes.provider.class</name> <value>org.apache.ranger.authorization.hadoop.RangerHdfsAuthorizer</value>
Ranger 插件的初始化过程
动态加载类
在Namenode启动过程中编译RangerHdfsAuthorizerorizer类(包名为ranger-hdfs-plugin-shim),将RangerHdfs的相关类动态加载进虚拟机,并实例化具体实现类RangerHdfsAuthorizerorizer(包名为ranger-hdfs-plugin)。
1 rangerPluginClassLoader = RangerPluginClassLoader.getInstance(RANGER_PLUGIN_TYPE, this.getClass()); 2 @SuppressWarnings("unchecked") 3 Class<INodeAttributeProvider> cls = (Class<INodeAttributeProvider>) Class.forName(RANGER_HDFS_AUTHORIZER_IMPL_CLASSNAME, true, rangerPluginClassLoader); 4 activatePluginClassLoader(); 5 rangerHdfsAuthorizerImpl = cls.newInstance();
插件初始化
初始化RangerPlugin,如上面的类图可知,RangerHdfsPlugin是RangerBasePlugin类的子类,其具体的初始化是由父类的初始化方法来实现的。该方法主要完成了以下几个功能:
(1)调用cleanup()方法,主要完成清空了refresher、serviceName、policyEngine这三个变量的值。
(2)读取配置文件,并设置以下变量的初始值。
- serviceType:Ranger提供访问控制服务的类型。
- serviceName:Ranger提供访问控制服务的名称。
- appId:由Ranger提供服务的组件ID。
- propertyPrefix:Ranger插件的属性前缀。
- pollingIntervalMs:刷新器定期更新策略的轮询间隔时间。Ranger 插件会定期从Ranger Admin拉取新的策略信息,并保存在Hdfs缓存中。
- cacheDir:从Ranger Admin拉取策略到Hdfs插件的临时存放目录。
(3)设置PangerPolicyEngineOptions类的成员变量值。
- evaluatorType:评估器的类型。在Ranger对Hdfs的访问权限的鉴权阶段需要策略评估器根据策略判断是否具有访问权限。
- cacheAuditResults:是否缓存审计。
- disableContextEnrichers:是否使用上下文增强器。
- disableCustomConditions:是否使用自定义条件。在Ranger0.5版本之后加入上下文增强器和用户自定义条件这样的“钩子”函数以增加授权策略的可扩展性。
- disableTagPolicyEvaluation:是否使用基于标签的策略评估。在Ranger0.6版本以后,Ranger不仅仅支持基于资源的策略,还支持基于标签的策略,该策略的优点是资源分类与访问授权的分离,标记的单个授权策略可用于授权跨各种Hadoop组件访问资源。
(4)调用createAdminClient(),创建RangerAdmin与RangerPlugin通信的客户端。这里使用的基于RESTful的通信风格,所以创建RangerAdminClient类的实例对象。
(5)创建PolicyRefresher类的对象,调用startRefresher()开启策略刷新器,根据轮询间隔时间定期从Ranger Admin 拉取更新的策略。
策略更新
Ranger插件更新策略的流程图如下:
这部分主要讲Ranger插件如何通过调用Ranger定义好的API获取策略。这里使用了jersey来构建RESTful服务。
(1)Jersey客户端的实现
RangerAdminRESTClient # getServicePoliciesIfUpdated()
1 if (isSecureMode) { 2 PrivilegedAction<ClientResponse> action = new PrivilegedAction<ClientResponse>() { 3 public ClientResponse run() { 4 // 创建WebResource实例,并根据URI和查询参数(lastKnownVersion、pluginId)构建URL 5 WebResource secureWebResource = createWebResource(RangerRESTUtils.REST_URL_POLICY_GET_FOR_SECURE_SERVICE_IF_UPDATED + serviceName) 6 .queryParam(RangerRESTUtils.REST_PARAM_LAST_KNOWN_POLICY_VERSION, Long.toString(lastKnownVersion)) 7 .queryParam(RangerRESTUtils.REST_PARAM_PLUGIN_ID, pluginId); 8 // 发送GET请求,并且mime类型为Json 9 return secureWebResource.accept(RangerRESTUtils.REST_MIME_TYPE_JSON).get(ClientResponse.class); 10 }; 11 }; 12 response = user.doAs(action); 13 }else{ 14 WebResource webResource = createWebResource(RangerRESTUtils.REST_URL_POLICY_GET_FOR_SERVICE_IF_UPDATED + serviceName) 15 .queryParam(RangerRESTUtils.REST_PARAM_LAST_KNOWN_POLICY_VERSION, Long.toString(lastKnownVersion)) 16 .queryParam(RangerRESTUtils.REST_PARAM_PLUGIN_ID, pluginId); 17 response = webResource.accept(RangerRESTUtils.REST_MIME_TYPE_JSON).get(ClientResponse.class); 18 } 19 if(response != null && response.getStatus() == 200) { //200:请求成功 20 ret = response.getEntity(ServicePolicies.class); 21 } else if(response != null && response.getStatus() == 304) { // 304:未修正 22 // no change 23 }
在RangerRESTUtils类中定义了参数的值:
public static final String REST_URL_POLICY_GET_FOR_SERVICE_IF_UPDATED = "/service/plugins/policies/download/"; public static final String SERVICE_NAME_PARAM = "serviceName"; public static final String LAST_KNOWN_TAG_VERSION_PARAM = "lastKnownVersion"; public static final String REST_MIME_TYPE_JSON = "application/json" ;
(2)服务端的实现
ServiceREST # getServicePolicesIfUpdate()
1 @GET 2 @Path("/policies/download/{serviceName}") 3 @Produces({ "application/json", "application/xml" }) 4 public ServicePolicies getServicePoliciesIfUpdated(@PathParam("serviceName") String serviceName, @QueryParam("lastKnownVersion") Long lastKnownVersion, @QueryParam("pluginId") String pluginId, @Context HttpServletRequest request) throws Exception { 5 ServicePolicies ret = null; 6 int httpCode = HttpServletResponse.SC_OK; 7 String logMsg = null; 8 RangerPerfTracer perf = null; 9 10 if (serviceUtil.isValidateHttpsAuthentication(serviceName, request)) { 11 if(lastKnownVersion == null) { 12 lastKnownVersion = Long.valueOf(-1); 13 } 14 try { 15 ServicePolicies servicePolicies = svcStore.getServicePoliciesIfUpdated(serviceName, lastKnownVersion); // 从数据库获取更新策略的过程 16 if(servicePolicies == null) { 17 httpCode = HttpServletResponse.SC_NOT_MODIFIED; 18 logMsg = "No change since last update"; 19 } else { 20 ret = filterServicePolicies(servicePolicies); 21 httpCode = HttpServletResponse.SC_OK; 22 logMsg = "Returning " + (ret.getPolicies() != null ? ret.getPolicies().size() : 0) + " policies. Policy version=" + ret.getPolicyVersion(); 23 } 24 } 25 } 26 return ret; 27 }