凌云时刻 · 技术

导读:Apache Ranger来源于XA Secure公司。2013年,XA Secure在加利福尼亚成立,专门做Hadoop生态的安全管控。2014年,Hortonworks收购了XA Secure,之后将XA Secure以新项目Apache Ranger贡献给了Apache软件基金会。Ranger进入了Apache孵化器项目。2017年3月,Ranger成为Apache的顶级项目之一。本文的介绍已2.1.0版本为基准。

作者 | 初晗

来源 | 凌云时刻(微信号:linuxpk)

1. 架构

Ranger属于C/S架构。其中Server端Ranger Admin提供授权策略的管理服务。用户可通过Web UI对用户、角色、组、授权策略进行变更。同时,这些管理能力也会通过REST API对外暴露。Ranger的Client端也就是plugin插件,通过REST API与Ranger Admin进行交互,定时拉取最新的权限策略并更新到plugin的缓存仓储中。对于Hadoop生态中不同的系统,Ranger提供了不同的plugin插件,目前已经覆盖了Hive、HDFS、Yarn、HBase、Kafka、Kudu、Solr等17类系统。每个插件实现了对应系统的访问控制相关的扩展接口,在特定的逻辑处理和模型转换之后,最终会对plugin通用common层的服务进行调用,包括权限管理、用户管理、角色管理、组管理、鉴权等。其中鉴权时,会对缓存仓储中的策略进行匹配。 

在扩展性方面,集成额外的系统,只需要为其实现相应的plugin即可。

2. 权限模型

 2.1 表结构

Ranger的持久化模型和领域模型是一一对应的关系。以下按照资源(service)和权限(policy)两部分来介绍。

 2.1.1 service

(1)service def 

在Ranger中,各类资源首先会按照service def来划分。一个service def代表一个类型,比如HDFS、HBASE、KUDU、KAFKA等。 

(2)service config def 

service config def约束了某个service def所需要的配置,这些配置可以设定为必须的或者可选的,可以设置默认值等。例如HIVE类型的config包含:username、password、jdbc.driverClassName、jdbc.url等。 

(3)service 

对应service def的一个具体的实例,也就是相应service def类型下的一个具体的集群。 

(4)service config map 

service config map包含具体的service集群的配置信息,这些配置信息受service config def的约束。 

(5)service version info 

service version info记录对应service的各种版本信息。某个service下,每当policy、role或者tag发生变更,都会更新service version info中相应的版本号。而plugin定时从Ranger Admin下载policy和role时都会携带版本信息,仅在版本变化的情况下,下载最新的policy和role并更新到plugin的缓存中。 

(6)resource def 

resource def描述的是某个类型service包含哪些类型的资源。比如HIVE类型的server包含的资源类型有:database、table、udf、column等。 

(7)context enricher def 

context enricher def定义了某个类型service的enricher。在鉴权等请求发生时,通过资源可以找到对应的service def,从而匹配到对应的enricher,再通过enricher补全请求的上下文信息。例如tag enricher会为请求添加相应资源的tag信息(下文会具体介绍tag)。 

(8)datamask type def 

datamask type def定义了一些对数据进行mask处理的方式。在权限策略中,如果配置了对应类型中的某个datamask,则会在满足权限策略的前提下对数据进行mask处理。例如对于HIVE类型的某个service集群,可以配置select操作的datamask策略为MASK_DATE_SHOW_YEAR,这样在HIVE查询到数据后,会对数据中的Date进行mask处理,只显示year。 

(9)policy condition def 

policy condition def定义了某个类型的service可以使用哪些策略条件类型。例如,为solr类型的集群添加权限策略,可以设置ip-ranger条件,也就是在请求ip符合规定条件的前提下,权限策略生效。 

(10)access type def 

access type def定义了某个类型集群的资源的访问方式都有哪些。比如对于HIVE类型的集群,可以通过select、update、create、drop等方式访问资源。 

(11)access type def grants 

access type def grants表示某个access type def所隐含的能力。比如HIVE类型集群资源的all访问方式,隐含着select、update、create、drop等访问方式。 

(12)enum def 

enum def与service config def配套使用。如果某个service def的配置项定义是enum类型,则该配置项的具体enum类型必须在该service def的enum def中定义。 

(13)enum element def 

enum elemet def与service config map配套使用。对于某个具体的service集群,如果其某个配置项是enum类型,则该配置项的可枚举值必须在enum elemet def中定义。

 2.1.2 policy

(1)policy 

一个policy就是一个service下的一个权限策略。分为三种类型: 

  • 访问控制策略:描述了主体(user、role、group)在什么条件下(condition)可以怎样访问(access)什么资源(resource)。 

  • 数据隐藏策略:描述了主体(user、role、group)在什么条件下(condition)怎样访问(access)什么资源(resource)的时候进行什么样的数据隐藏(datamask)。每个类型service可以选择的datamask在上面提到的datamask type def中定义。 

  • 行级过滤策略:描述了主体(user、role、group)在什么条件下(condition)怎样访问(access)什么资源(resource)的时候进行什么样的行数据过滤。

(2)policy label 

可以为每个policy添加一个或多个标签。标签仅用于标识,不影响实际的权限管理和鉴权逻辑。在搜索策略时,可指定标签作为筛选条件。 

(3)policy item 

一个policy可以包含多条policy item。每条policy item包含除了resource之外的其他元组信息。比如访问控制策略的policy item,包含user、role、group、permission这些信息,其中user、role、group属于主体,permission就是访问方式(access type)。每个policy下所有的policy item作用的资源都是一样的。 

Ranger中,虽然定义了policy item的表结构,但数据并未持久化到policy item表中。整个policy的内容,转换成json大字段存储在policy表的policy_text字段中,包括策略作用的resource和每一条policy item的具体内容。

 2.2 授权和撤权

授权和撤权变更的都是访问控制策略。

 2.2.1 Ranger Admin

授权和撤权的一种方式是通过Ranger Admin进行,在Ranger Admin上为某个service添加、修改或删除访问控制policy。

操作的方式包括: 

  • Web UI

  • REST API 

Ranger Plugin会定时从Ranger Admin拉取最新的policy。

 2.2.2 Ranger Plugin

每个类型的plugin实现了对应系统开放的访问控制接口。通过该接口进行授权和撤权,会进入Ranger Plugin的common模块,调用RangerBasePlugin的grantAccess和revokeAccess方法进行授权和撤权:

public void grantAccess(GrantRevokeRequest request, RangerAccessResultProcessor resultProcessor);
public void revokeAccess(GrantRevokeRequest request, RangerAccessResultProcessor resultProcessor);

而授权和撤权会通过REST API在Ranger Admin执行: 

  • 对于授权请求,会先检查是否已存在对应resource的访问控制类型的policy。如果存在,则将授权信息更新到policy中;如果不存在,则为service新建policy。 

  • 对于撤销权限,先查找对应resource的访问控制类型的policy。如果存在,则根据撤权请求中的use、role、group,从policy的各个policy item中提取出已有的access,再将这些access与撤权请求中的access做减法;如果不存在,则什么也不做。

 2.3 鉴权

鉴权逻辑主要在plugin中,将主体对resource的访问与plugin缓存的访问控制策略进行匹配,从而判别主体是否可以访问目标resource。 

对于访问控制policy,policy item分为4种类型:

allow:允许配置的主体对资源进行指定的访问。例如:下图中,主体为admin用户,permission为select,表示允许admin用户对资源进行select访问。


allow exception:通常配合allow使用,从allow的配置中进行排除。例如:下图中,allow条件允许public组对资源进行select访问,而allow exception条件从中排除了test用户对资源的select访问。也就是说,如果test用户属于public组,仍然不能对资源进行select操作,而public组中的其他用户则可以。

deny:拒绝访问。例如:下图中的拒绝条件配置,表示拒绝admin用户对资源进行Drop操作。

deny exception:配合deny使用,从deny的配置中进行排除。例如:下图中,deny条件表示拒绝public组对资源进行Drop操作,而deny exception条件从deny条件中排除了test用户对资源的select访问。换句话说,如果test用户属于public组,其对资源进行的Drop操作并没有被拒绝,如果存在额外的允许条件,则可以进行相应操作。

除了4种访问控制policy item类型之外,还有一个“deny all else”开关与allow条件配合使用。对于某个allow条件,如果开启了“deny all else”,则在allow条件没有匹配的情况下,直接拒绝访问。如果关闭了“deny all else”,则会继续匹配其他的policy item,有可能会匹配到其他的allow条件并允许访问。 

下图描述了具体的Ranger鉴权流程,在整个流程中,4个访问控制policy item类型与“deny all else”开关组合使用。4个policy item类型的优先级为:deny exception > deny > allow exception > allow。

3. 功能介绍

 3.1 Tag

上面介绍权限模型时,提到了tag。在Ranger中,一个tag标签代表了一个policy集合。每个service可以关联一个tag,从而间接拥有了tag的policy。在模型层面,tag是一个特殊的service类型(service def),一个具体的tag标签就是一个service实例,所以tag的policy管理方式与service的policy管理方式是相同的。

鉴权时,会先检查对应的service是否存在tag policy,如果存在,则使用tag policy进行匹配决策。如果tag policy产生了决策结果,会根据tag policy与service policy的优先级,判断是否进一步进行service policy的匹配决策。 

简单来说,通过tag标签的方式,可以简化多service之间共性policy的管理。比如,多个service的policy集合存在着相同的部分,则可以将这些相同的部分提取出来定义为一个tag,然后将这些service关联到这个tag上,这样就不需要为每个service单独维护相同的policy,对tag policy进行变更即可作用到这些service上。

 3.2 Label

Ranger允许为每个policy添加多个不同的label标签,每个label都是policy本身信息的一种标志。Ranger的policy查询接口,可以将label作为过滤条件,查询出具有指定label信息的policy。

 3.3 Audit

Ranger提供了audit审计的能力,记录资源的访问信息、各plugin的状态信息以及Web的session信息等,并且可以配置将审计信息记录在哪个介质中。目前Ranger支持5种审计存储介质:DB、HDFS、log4j、Kafka、Solr。同时,Ranger Admin Web UI可以查看已记录的审计信息,便于跟踪用户的访问。

 3.4 Security Zone

Security Zone安全域为Ranger带来了隔离管理的能力。可以将service下的resource添加到指定的zone中,也可以在service下添加指定zone的policy。鉴权时,如果目标资源属于某个zone,则使用该zone的policy进行决策,否则使用default zone的policy进行决策。一个资源只能属于一个zone,每个zone都需要单独设置管理员,从这个角度看,zone的概念类似于租户。

 3.5 Row Filter、Data Mask


Ranger支持Row Filter和Data Mask。Row Filter是指行级过滤,也可以叫做行权限,将权限管理的粒度控制到行级别。通过配置Row Filter类型的policy,可以控制主体在访问资源时,可以访问哪些行。Data Mask是指数据隐藏。通过配置Data Mask类型的policy,可以控制主体在访问资源时,对数据进行怎样的隐藏处理。 


目前Ranger仅对Hive支持Row Filter和Data Mask,因为Hive开放了Row Filter和Data Mask的扩展接口。Hive集成Ranger后,在数据查询时,会根据Ranger中配置的Row Filter和Data Mask类型的policy对查询HiveQL进行改写,查询结果中仅包含主体可访问的行,并且进行了必要的数据隐藏处理。对于Row Filter的改写,是在HiveQL中拼接where条件;对于Data Mask的改写,是在HiveQL中使用函数替换column。因此Ranger的Data Mask类型必须是Hive能够支持的函数,例如:mask、mask_hash、mask_show_first_n()等。 


可以看到,通过Row Filter和Data Mask机制,可以做到更细粒度的敏感数据保护。

4. 代码解读


在plugin层面,以Hive Plugin为例。

 4.1 授权/撤权

Hive开放的访问控制扩展接口可关注两个: 

1. org.apache.hadoop.hive.ql.security.authorization.plugin.HiveAuthorizerFactory

用于生成具体的HiveAuthorizer实现。 

2. org.apache.hadoop.hive.ql.security.authorization.plugin.HiveAuthorizer

用于权限管理、鉴权、角色管理等。

Ranger Hive Plugin的实现类分别为:

1. org.apache.ranger.authorization.hive.authorizer.RangerHiveAuthorizerFactory

在创建HiveAuthorizer时,返回RangerHiveAuthorizer实现

2. org.apache.ranger.authorization.hive.authorizer.RangerHiveAuthorizer

Ranger实现的权限管理、鉴权、角色管理等逻辑。

在调用grantPrivileges/revokePrivileges进行授权/撤权时,先将Hive模型转换为Ranger模型,然后调用RangerHivePlugin组件:

public void grantPrivileges(List<HivePrincipal> hivePrincipals,
                            List<HivePrivilege> hivePrivileges,
                            HivePrivilegeObject hivePrivObject,
                            HivePrincipal       grantorPrincipal,
                            boolean             grantOption)
    throws HiveAuthzPluginException, HiveAccessControlException {
    RangerHiveAuditHandler auditHandler = new RangerHiveAuditHandler();
    try {
        List<HivePrivilegeObject> outputs = new ArrayList<>(Arrays.asList(hivePrivObject));
        RangerHiveResource resource = getHiveResource(HiveOperationType.GRANT_PRIVILEGE, hivePrivObject, null, outputs);
        GrantRevokeRequest request  = createGrantRevokeData(resource, hivePrincipals, hivePrivileges, grantorPrincipal, grantOption);
        hivePlugin.grantAccess(request, auditHandler);
    } catch(Exception excp) {
        throw new HiveAccessControlException(excp);
    }
}

RangerHivePlugin是RangerBasePlugin的子类,会在初始化阶段生成一些Hive特有的配置参数,核心逻辑就像3.2.2中提到的那样,在RangerBasePlugin公共模块中。RangerBasePlugin会将授权/撤权请求通过REST API发送到Ranger Admin进行处理:

public void grantAccess(GrantRevokeRequest request, RangerAccessResultProcessor resultProcessor) throws Exception {
    boolean isSuccess = false;
    try {
        RangerPolicyEngine policyEngine = this.policyEngine;
        if (policyEngine != null) {
            request.setZoneName(policyEngine.getMatchedZoneName(request));
        }
        getAdminClient().grantAccess(request);
        isSuccess = true;
    } finally {
        auditGrantRevoke(request, "grant", isSuccess, resultProcessor);
    }
}

对于授权,先检查是否存在对应resource的policy。如果存在,进入策略更新处理逻辑:

RangerPolicy policy = getExactMatchPolicyForResource(serviceName, resource, userName);
if(policy != null) {
    boolean policyUpdated = false;
    policyUpdated = ServiceRESTUtil.processGrantRequest(policy, grantRequest);
    if(policyUpdated) {
        policy.setZoneName(grantRequest.getZoneName());
        svcStore.updatePolicy(policy);
    } else {
        throw new Exception("processGrantRequest processing failed");
    }
} else {
    // ...
}

其中ServiceRESTUtil.processGrantRequest方法的作用是将授权请求合并到已有的policy中。根据授权请求构建一个新的policy,然后调用processApplyPolicy方法进行新、老policy的合并:

static public boolean processGrantRequest(RangerPolicy policy, GrantRevokeRequest grantRequest) {
    boolean policyUpdated = false;
    
    //Build a policy and set up policyItem in it to mimic grant request
    RangerPolicy appliedPolicy = new RangerPolicy();
    RangerPolicy.RangerPolicyItem policyItem = new RangerPolicy.RangerPolicyItem();
    policyItem.setDelegateAdmin(grantRequest.getDelegateAdmin());
    policyItem.getUsers().addAll(grantRequest.getUsers());
    policyItem.getGroups().addAll(grantRequest.getGroups());
    policyItem.getRoles().addAll(grantRequest.getRoles());
    List<RangerPolicy.RangerPolicyItemAccess> accesses = new ArrayList<RangerPolicy.RangerPolicyItemAccess>();
    Set<String> accessTypes = grantRequest.getAccessTypes();
    for (String accessType : accessTypes) {
        accesses.add(new RangerPolicy.RangerPolicyItemAccess(accessType, true));
    }
    policyItem.setAccesses(accesses);
    appliedPolicy.getPolicyItems().add(policyItem);


    processApplyPolicy(policy, appliedPolicy);


    policyUpdated = true;


    return policyUpdated;
}


static public void processApplyPolicy(RangerPolicy existingPolicy, RangerPolicy appliedPolicy) {
    processApplyPolicyForItemType(existingPolicy, appliedPolicy, POLICYITEM_TYPE.ALLOW);
    processApplyPolicyForItemType(existingPolicy, appliedPolicy, POLICYITEM_TYPE.DENY);
    processApplyPolicyForItemType(existingPolicy, appliedPolicy, POLICYITEM_TYPE.ALLOW_EXCEPTIONS);
    processApplyPolicyForItemType(existingPolicy, appliedPolicy, POLICYITEM_TYPE.DENY_EXCEPTIONS);
}

processApplyPolicyForItemType是合并逻辑的核心处理方法。处理过程包括以下几个步骤:

  1. 从新policy中,按照user、role、group的维度,分离出各自的policy item

  2. 从老policy中,分离出新policy中user、role、group对应的policy item

  3. 将(1)和(2)中的新、老polity item按照user、role、group的维度进行合并

  4. 将(3)中合并的结果添加回老policy中

static private void processApplyPolicyForItemType(RangerPolicy existingPolicy, RangerPolicy appliedPolicy, POLICYITEM_TYPE policyItemType) {
    List<RangerPolicy.RangerPolicyItem> appliedPolicyItems = null;


    switch (policyItemType) {
        case ALLOW:
            appliedPolicyItems = appliedPolicy.getPolicyItems();
            break;
        case DENY:
            appliedPolicyItems = appliedPolicy.getDenyPolicyItems();
            break;
        case ALLOW_EXCEPTIONS:
            appliedPolicyItems = appliedPolicy.getAllowExceptions();
            break;
        case DENY_EXCEPTIONS:
            appliedPolicyItems = appliedPolicy.getDenyExceptions();
            break;
        default:
            LOG.warn("processApplyPolicyForItemType(): invalid policyItemType=" + policyItemType);
    }


    if (CollectionUtils.isNotEmpty(appliedPolicyItems)) {


        Set<String> users = new HashSet<String>();
        Set<String> groups = new HashSet<String>();
        Set<String> roles = new HashSet<String>();


        Map<String, RangerPolicy.RangerPolicyItem[]> userPolicyItems = new HashMap<String, RangerPolicy.RangerPolicyItem[]>();
        Map<String, RangerPolicy.RangerPolicyItem[]> groupPolicyItems = new HashMap<String, RangerPolicy.RangerPolicyItem[]>();
        Map<String, RangerPolicy.RangerPolicyItem[]> rolePolicyItems = new HashMap<String, RangerPolicy.RangerPolicyItem[]>();


        // Extract users, groups, and roles specified in appliedPolicy items
        extractUsersGroupsAndRoles(appliedPolicyItems, users, groups, roles);


        // Split existing policyItems for users, groups, and roles extracted from appliedPolicyItem into userPolicyItems, groupPolicyItems, and rolePolicyItems
        splitExistingPolicyItems(existingPolicy, users, userPolicyItems, groups, groupPolicyItems, roles, rolePolicyItems);


        // Apply policyItems of given type in appliedPolicy to policyItems extracted from existingPolicy
        applyPolicyItems(appliedPolicyItems, policyItemType, userPolicyItems, groupPolicyItems, rolePolicyItems);


        // Add modified/new policyItems back to existing policy
        mergeProcessedPolicyItems(existingPolicy, userPolicyItems, groupPolicyItems, rolePolicyItems);


        compactPolicy(existingPolicy);
    }
}


如果授权请求中的resource对应的policy不存在,则为service新建policy:


RangerPolicy policy = getExactMatchPolicyForResource(serviceName, resource, userName);
if(policy != null) {
    // ...
} else {
    policy = new RangerPolicy();
    policy.setService(serviceName);
    policy.setName("grant-" + System.currentTimeMillis()); // TODO: better policy name
    policy.setDescription("created by grant");
    policy.setIsAuditEnabled(grantRequest.getEnableAudit());
    policy.setCreatedBy(userName);
    
    Map<String, RangerPolicyResource> policyResources = new HashMap<String, RangerPolicyResource>();
    Set<String>                       resourceNames   = resource.getKeys();
    if(! CollectionUtils.isEmpty(resourceNames)) {
        for(String resourceName : resourceNames) {
            RangerPolicyResource policyResource = new RangerPolicyResource((String) resource.getValue(resourceName));
            policyResource.setIsRecursive(grantRequest.getIsRecursive());


            policyResources.put(resourceName, policyResource);
        }
    }
    policy.setResources(policyResources);


    RangerPolicyItem policyItem = new RangerPolicyItem();
    policyItem.setDelegateAdmin(grantRequest.getDelegateAdmin());
    policyItem.getUsers().addAll(grantRequest.getUsers());
    policyItem.getGroups().addAll(grantRequest.getGroups());
    policyItem.getRoles().addAll(grantRequest.getRoles());
    for(String accessType : grantRequest.getAccessTypes()) {
        policyItem.getAccesses().add(new RangerPolicyItemAccess(accessType, Boolean.TRUE));
    }
    policy.getPolicyItems().add(policyItem);
    policy.setZoneName(grantRequest.getZoneName());


    svcStore.createPolicy(policy);
}


对于撤权,同样会先检查是否存在对应resource的policy。如果不存在,则什么也不做。如果存在,则进入策略更新处理逻辑:

RangerPolicy policy = getExactMatchPolicyForResource(serviceName, resource, userName);
if(policy != null) {
    boolean policyUpdated = false;
    policyUpdated = ServiceRESTUtil.processRevokeRequest(policy, revokeRequest);
    if(policyUpdated) {
        policy.setZoneName(revokeRequest.getZoneName());
        svcStore.updatePolicy(policy);
    } else {
        throw new Exception("processRevokeRequest processing failed");
    }
}

其中ServiceRESTUtil.processRevokeRequest的作用是从已有policy中将撤权请求相关的内容删除,删除过程包括以下几个步骤:

  1. 根据授权请求构造新的policy

  2. 从新policy中,按照user、role、group的维度,分离出各自的policy item

  3. 从老policy中,分离出新policy中user、role、group对应的policy item

  4. 按照user、role、group维度,从(2)中老policy item中,删除(1)中新policy item的access

  5. 将(4)中删除的结果合并回老policy中

static public boolean processRevokeRequest(RangerPolicy existingRangerPolicy, GrantRevokeRequest revokeRequest) {
    boolean policyUpdated = false;


    //Build a policy and set up policyItem in it to mimic revoke request
    RangerPolicy appliedRangerPolicy = new RangerPolicy();
    RangerPolicy.RangerPolicyItem appliedRangerPolicyItem = new RangerPolicy.RangerPolicyItem();
    appliedRangerPolicyItem.setDelegateAdmin(revokeRequest.getDelegateAdmin());
    appliedRangerPolicyItem.getUsers().addAll(revokeRequest.getUsers());
    appliedRangerPolicyItem.getGroups().addAll(revokeRequest.getGroups());
    appliedRangerPolicyItem.getRoles().addAll(revokeRequest.getRoles());
    List<RangerPolicy.RangerPolicyItemAccess> appliedRangerPolicyItemAccess = new ArrayList<RangerPolicy.RangerPolicyItemAccess>();
    Set<String> appliedPolicyItemAccessType = revokeRequest.getAccessTypes();
    for (String accessType : appliedPolicyItemAccessType) {
        appliedRangerPolicyItemAccess.add(new RangerPolicy.RangerPolicyItemAccess(accessType, false));
    }
    appliedRangerPolicyItem.setAccesses(appliedRangerPolicyItemAccess);
    appliedRangerPolicy.getPolicyItems().add(appliedRangerPolicyItem);


    List<RangerPolicy.RangerPolicyItem> appliedRangerPolicyItems = appliedRangerPolicy.getPolicyItems();
    if (CollectionUtils.isNotEmpty(appliedRangerPolicyItems)) {
        Set<String> users = new HashSet<String>();
        Set<String> groups = new HashSet<String>();
        Set<String> roles = new HashSet<>();


        Map<String, RangerPolicy.RangerPolicyItem[]> userPolicyItems = new HashMap<String, RangerPolicy.RangerPolicyItem[]>();
        Map<String, RangerPolicy.RangerPolicyItem[]> groupPolicyItems = new HashMap<String, RangerPolicy.RangerPolicyItem[]>();
        Map<String, RangerPolicy.RangerPolicyItem[]> rolePolicyItems = new HashMap<String, RangerPolicy.RangerPolicyItem[]>();


        // Extract users, groups, and roles specified in appliedPolicy items
        extractUsersGroupsAndRoles(appliedRangerPolicyItems, users, groups, roles);


        // Split existing policyItems for users, groups, and roles extracted from appliedPolicyItem into userPolicyItems, groupPolicyItems and rolePolicyItems
        splitExistingPolicyItems(existingRangerPolicy, users, userPolicyItems, groups, groupPolicyItems, roles, rolePolicyItems);


        for (RangerPolicy.RangerPolicyItem tempPolicyItem : appliedRangerPolicyItems) {
            List<String> appliedPolicyItemsUser = tempPolicyItem.getUsers();
            for (String user : appliedPolicyItemsUser) {
                RangerPolicy.RangerPolicyItem[] rangerPolicyItems = userPolicyItems.get(user);
                if(rangerPolicyItems!=null && rangerPolicyItems.length>0){
                    if(rangerPolicyItems[POLICYITEM_TYPE.ALLOW.ordinal()]!=null){
                        removeAccesses(rangerPolicyItems[POLICYITEM_TYPE.ALLOW.ordinal()], tempPolicyItem.getAccesses());
                        if(!CollectionUtils.isEmpty(rangerPolicyItems[POLICYITEM_TYPE.ALLOW.ordinal()].getAccesses())){
                            rangerPolicyItems[POLICYITEM_TYPE.ALLOW.ordinal()].setDelegateAdmin(revokeRequest.getDelegateAdmin());
                        }else{
                            rangerPolicyItems[POLICYITEM_TYPE.ALLOW.ordinal()].setDelegateAdmin(Boolean.FALSE);
                        }
                    }
                    if(rangerPolicyItems[POLICYITEM_TYPE.DENY_EXCEPTIONS.ordinal()]!=null){
                        removeAccesses(rangerPolicyItems[POLICYITEM_TYPE.DENY_EXCEPTIONS.ordinal()], tempPolicyItem.getAccesses());
                        rangerPolicyItems[POLICYITEM_TYPE.DENY_EXCEPTIONS.ordinal()].setDelegateAdmin(Boolean.FALSE);
                    }
                }
            }
        }
        for (RangerPolicy.RangerPolicyItem tempPolicyItem : appliedRangerPolicyItems) {
            List<String> appliedPolicyItemsGroup = tempPolicyItem.getGroups();
            for (String group : appliedPolicyItemsGroup) {
                RangerPolicy.RangerPolicyItem[] rangerPolicyItems = groupPolicyItems.get(group);
                if(rangerPolicyItems!=null && rangerPolicyItems.length>0){
                    if(rangerPolicyItems[POLICYITEM_TYPE.ALLOW.ordinal()]!=null){
                        removeAccesses(rangerPolicyItems[POLICYITEM_TYPE.ALLOW.ordinal()], tempPolicyItem.getAccesses());
                        if(!CollectionUtils.isEmpty(rangerPolicyItems[POLICYITEM_TYPE.ALLOW.ordinal()].getAccesses())){
                            rangerPolicyItems[POLICYITEM_TYPE.ALLOW.ordinal()].setDelegateAdmin(revokeRequest.getDelegateAdmin());
                        }else{
                            rangerPolicyItems[POLICYITEM_TYPE.ALLOW.ordinal()].setDelegateAdmin(Boolean.FALSE);
                        }
                    }
                    if(rangerPolicyItems[POLICYITEM_TYPE.DENY_EXCEPTIONS.ordinal()]!=null){
                        removeAccesses(rangerPolicyItems[POLICYITEM_TYPE.DENY_EXCEPTIONS.ordinal()], tempPolicyItem.getAccesses());
                        rangerPolicyItems[POLICYITEM_TYPE.DENY_EXCEPTIONS.ordinal()].setDelegateAdmin(Boolean.FALSE);
                    }
                }
            }
        }


        for (RangerPolicy.RangerPolicyItem tempPolicyItem : appliedRangerPolicyItems) {
            List<String> appliedPolicyItemsRole = tempPolicyItem.getRoles();
            for (String role : appliedPolicyItemsRole) {
                RangerPolicy.RangerPolicyItem[] rangerPolicyItems = rolePolicyItems.get(role);
                if(rangerPolicyItems!=null && rangerPolicyItems.length>0){
                    if(rangerPolicyItems[POLICYITEM_TYPE.ALLOW.ordinal()]!=null){
                        removeAccesses(rangerPolicyItems[POLICYITEM_TYPE.ALLOW.ordinal()], tempPolicyItem.getAccesses());
                        if(!CollectionUtils.isEmpty(rangerPolicyItems[POLICYITEM_TYPE.ALLOW.ordinal()].getAccesses())){
                            rangerPolicyItems[POLICYITEM_TYPE.ALLOW.ordinal()].setDelegateAdmin(revokeRequest.getDelegateAdmin());
                        }else{
                            rangerPolicyItems[POLICYITEM_TYPE.ALLOW.ordinal()].setDelegateAdmin(Boolean.FALSE);
                        }
                    }
                    if(rangerPolicyItems[POLICYITEM_TYPE.DENY_EXCEPTIONS.ordinal()]!=null){
                        removeAccesses(rangerPolicyItems[POLICYITEM_TYPE.DENY_EXCEPTIONS.ordinal()], tempPolicyItem.getAccesses());
                        rangerPolicyItems[POLICYITEM_TYPE.DENY_EXCEPTIONS.ordinal()].setDelegateAdmin(Boolean.FALSE);
                    }
                }
            }
        }
        // Add modified/new policyItems back to existing policy
        mergeProcessedPolicyItems(existingRangerPolicy, userPolicyItems, groupPolicyItems, rolePolicyItems);
        compactPolicy(existingRangerPolicy);
    }


    policyUpdated = true;
    return policyUpdated;
}

 4.2 鉴权

对于Ranger Hive Plugin来说,鉴权会调用RangerHiveAuthorizer的checkPrivileges方法。

RangerHiveAuthorizer.checkPrivileges在模型转化那之后,调用RangerBasePlugin的isAccessAllowed方法进入策略匹配逻辑:

public RangerAccessResult isAccessAllowed(RangerAccessRequest request, RangerAccessResultProcessor resultProcessor) {
    RangerPolicyEngine policyEngine = this.policyEngine;
    if(policyEngine != null) {
        return policyEngine.evaluatePolicies(request, RangerPolicy.POLICY_TYPE_ACCESS, resultProcessor);
    }
    return null;
}

策略匹配由RangerPolicyEngine负责,通过evaluatePolicies方法,评估访问请求是否被允许。在评估过程中,如果service有tag policy,会先评估tag policy。之后再遍历service policy,根据tag policy和service policy的优先级,决定是使用tag policy的决策结果还是继续进行service policy的评估。如果service没有tag policy,则直接遍历service policy进行评估:

public RangerAccessResult evaluatePolicies(RangerAccessRequest request, int policyType, RangerAccessResultProcessor resultProcessor) {
    requestProcessor.preProcess(request);
    RangerAccessResult ret = zoneAwareAccessEvaluationWithNoAudit(request, policyType);
    if (resultProcessor != null) {
        resultProcessor.processResult(ret);
    }
    return ret;
}


private RangerAccessResult zoneAwareAccessEvaluationWithNoAudit(RangerAccessRequest request, int policyType) {
    RangerAccessResult     ret                 = null;
    RangerPolicyRepository policyRepository    = policyEngine.getPolicyRepository();
    RangerPolicyRepository tagPolicyRepository = policyEngine.getTagPolicyRepository();
    String                 zoneName            = policyEngine.getMatchedZoneName(request.getResource()); // Evaluate zone-name from request


    if (StringUtils.isNotEmpty(zoneName)) {
        policyRepository = policyEngine.getZonePolicyRepositories().get(zoneName);
        if (policyRepository == null) {
            LOG.error("policyRepository for zoneName:[" + zoneName + "],  serviceName:[" + policyEngine.getPolicyRepository().getServiceName() + "], policyVersion:[" + getPolicyVersion() + "] is null!! ERROR!");
        }
    }


    if (policyRepository != null) {
        ret = evaluatePoliciesNoAudit(request, policyType, zoneName, policyRepository, tagPolicyRepository);
        ret.setZoneName(zoneName);
    }


    return ret;
}


private RangerAccessResult evaluatePoliciesNoAudit(RangerAccessRequest request, int policyType, String zoneName, RangerPolicyRepository policyRepository, RangerPolicyRepository tagPolicyRepository) {


    final Date               accessTime  = request.getAccessTime() != null ? request.getAccessTime() : new Date();
    final RangerAccessResult ret         = createAccessResult(request, policyType);


    evaluateTagPolicies(request, policyType, zoneName, tagPolicyRepository, ret);


    boolean isAllowedByTags          = ret.getIsAccessDetermined() && ret.getIsAllowed();
    boolean isDeniedByTags           = ret.getIsAccessDetermined() && !ret.getIsAllowed();
    boolean evaluateResourcePolicies = policyEngine.hasResourcePolicies(policyRepository);


    if (evaluateResourcePolicies) {
        boolean findAuditByResource = !ret.getIsAuditedDetermined();
        boolean foundInCache        = findAuditByResource && policyRepository.setAuditEnabledFromCache(request, ret);


        List<RangerPolicyEvaluator> evaluators = policyRepository.getLikelyMatchPolicyEvaluators(request.getResource(), policyType);


        for (RangerPolicyEvaluator evaluator : evaluators) {
            if (!evaluator.isApplicable(accessTime)) {
                continue;
            }
            if (isDeniedByTags) {
                if (ret.getPolicyPriority() >= evaluator.getPolicyPriority()) {
                    ret.setIsAccessDetermined(true);
                }
            } else if (isAllowedByTags) {
                if (ret.getPolicyPriority() > evaluator.getPolicyPriority()) {
                    ret.setIsAccessDetermined(true);
                }
            }
            ret.incrementEvaluatedPoliciesCount();
            evaluator.evaluate(request, ret);
            if (ret.getIsAllowed()) {
                if (!evaluator.hasDeny()) { // No more deny policies left
                    ret.setIsAccessDetermined(true);
                }
            }
            if (ret.getIsAuditedDetermined() && ret.getIsAccessDetermined()) {
                break;            // Break out of policy-evaluation loop
            }
        }
        
        if (!ret.getIsAccessDetermined()) {
            if (isDeniedByTags) {
                ret.setIsAllowed(false);
            } else if (isAllowedByTags) {
                ret.setIsAllowed(true);
            }
        }
        if (ret.getIsAllowed()) {
            ret.setIsAccessDetermined(true);
        }
        if (findAuditByResource && !foundInCache) {
            policyRepository.storeAuditEnabledInCache(request, ret);
        }
    }
    return ret;
}

Ranger会为每个policy维护一个RangerPolicyEvaluator, 在RangerPolicyEvaluator中评估policy对访问请求的决策结果。而policy的评估,又是通过匹配policy item进行的。RangerDefaultPolicyEvaluator.getMatchingPolicyItem中的policy item匹配逻辑,体现了3.3中描述的优先级关系:

public void evaluate(RangerAccessRequest request, RangerAccessResult result) {
    if (request != null && result != null) {
        if (!result.getIsAccessDetermined() || !result.getIsAuditedDetermined()) {
            //Evaluate Policy Level Custom Conditions, if any and allowed then go ahead for policyItem level evaluation
            if(matchPolicyCustomConditions(request)) {
                if (!result.getIsAuditedDetermined()) {
                    if (isAuditEnabled()) {
                        result.setIsAudited(true);
                        result.setAuditPolicyId(getPolicy().getId());
                    }
                }
                if (!result.getIsAccessDetermined()) {
                    if (hasMatchablePolicyItem(request)) {
                        evaluatePolicyItems(request, matchType, result);
                    }
                }
            }
        }
    }
}


protected void evaluatePolicyItems(RangerAccessRequest request, RangerPolicyResourceMatcher.MatchType matchType, RangerAccessResult result) {
    RangerPolicyItemEvaluator matchedPolicyItem = getMatchingPolicyItem(request, result);
    if (matchedPolicyItem != null) {
        matchedPolicyItem.updateAccessResult(this, result, matchType);
    } else if (getPolicy().getIsDenyAllElse() && (getPolicy().getPolicyType() == null || getPolicy().getPolicyType() == RangerPolicy.POLICY_TYPE_ACCESS) && !request.isAccessTypeAny()) {
        updateAccessResult(result, RangerPolicyResourceMatcher.MatchType.NONE, false, "matched deny-all-else policy");
    }
}


protected RangerPolicyItemEvaluator getMatchingPolicyItem(RangerAccessRequest request, RangerAccessResult result) {
    RangerPolicyItemEvaluator ret = null;
    Integer policyType = getPolicy().getPolicyType();
    if (policyType == null) {
        policyType = RangerPolicy.POLICY_TYPE_ACCESS;
    }
    switch (policyType) {
        case RangerPolicy.POLICY_TYPE_ACCESS: {
            ret = getMatchingPolicyItem(request, denyEvaluators, denyExceptionEvaluators);


            if(ret == null && !result.getIsAccessDetermined()) { // a deny policy could have set isAllowed=true, but in such case it wouldn't set isAccessDetermined=true
                ret = getMatchingPolicyItem(request, allowEvaluators, allowExceptionEvaluators);
            }
            break;
        }
        case RangerPolicy.POLICY_TYPE_DATAMASK: {
            ret = getMatchingPolicyItem(request, dataMaskEvaluators);
            break;
        }
        case RangerPolicy.POLICY_TYPE_ROWFILTER: {
            ret = getMatchingPolicyItem(request, rowFilterEvaluators);
            break;
        }
        default:
            break;
    }
    return ret;
}

END

往期精彩文章回顾

吴翰清:有变革的需求,才有技术的诞生

云原生时代,消息中间件的演进路线

如何提升微服务的幸福感

OceanBase 连破纪录的背后,是技术人的砥砺前行

重磅!解读国内唯一入选全球顶会SIGCOMM论文

一文读懂数据湖

阿里云飞天洛神:高性能网络软硬件一体化技术实践

为什么钉钉里的图片打开得更快了?

长按扫描二维码关注凌云时刻

每日收获前沿技术与科技洞见

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐