简介

Apache Ranger为Hadoop体系提供了同意的安全体系,包括访问权限控制和统一的审计(记录谁访问Ranger进行权限设置或者校验等操作)。如果想要开发一个Ranger的插件主要三个部分:

Ranger服务端:需要定义一个服务类型JSON文件上传给Ranger Service,以及实现一个RangerBaseService类作为Ranger服务的资源查找,或者配置检验的jar包放到Ranger服务的range-plugins/目录。

Ranger鉴权的插件:根据Ranger提供的接口实现一个鉴权的插件,这个插件会定时从Ranger-Service端将权限同步到本地,需要鉴权的服务可以用对应的接口来进行权限校验。很多服务例如doris,hive是将这个插件集成到了他们的服务中,当然也可以拿出来单独使用,例如自己解析Sql语句拿到用户以及对应表时在调用插件接口进行鉴权。

Ranger授权api:这一部分可以通过Ranger提供的UI手动进行添加,ranger也提供了api,用户可以通过api进行新增权限,或者删除更改权限等。权限这块Ranger内称之为RangerPolicy策略。
image-20241231173211402.png

Ranger服务端

实现RangerBaseService类

public class RangerServicePaimon extends RangerBaseService {
    @Override
    public Map<String, Object> validateConfig() {

        return new HashMap<>();
    }
    @Override
    public List<String> lookupResource(ResourceLookupContext resourceLookupContext) throws Exception {

        return new ArrayList<>();
    }
}

此类有两个方法,都是用于在Ranger Service UI上做资源查询,或者配置检验时用的,可以不做实现,直接返回空也没问题。之后需要将实现的jar包放入range-plugins/目录下之后重启Ranger服务。

服务定义描述文件

{
  "name": "paimon",
  "displayName": "Paimon",
    //对应jar包的service实现类
  "implClass": "com.jiduauto.ranger.service.paimon.RangerServicePaimon", 
  "label": "Paimon",
  "description": "Paimon",
  //需要进行权限检验的资源
  "resources": [
    {
      "itemId": 1,
      "name": "catalog",
      "type": "string",
      "level": 10,
      "parent": "",
      "mandatory": true,
      "isValidLeaf": true,
      "lookupSupported": true,
      "recursiveSupported": false,
      "excludesSupported": true,
      "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher",
      "matcherOptions": {
        "wildCard": true,
        "ignoreCase": true
      },
      "validationRegEx": "",
      "validationMessage": "",
      "uiHint": "",
      "accessTypeRestrictions": [
        "create",
        "show",
        "alter",
        "drop"
      ],
      "label": "Paimon Catalog",
      "description": "Paimon Catalog"
    },
    {
      "itemId": 2,
      "name": "database",
      "type": "string",
      "level": 20,
      "parent": "catalog",
      "mandatory": true,
      "isValidLeaf": true,
      "lookupSupported": true,
      "recursiveSupported": false,
      "excludesSupported": true,
      "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher",
      "matcherOptions": {
        "wildCard": true,
        "ignoreCase": true
      },
      "validationRegEx": "",
      "validationMessage": "",
      "uiHint": "",
      "accessTypeRestrictions": [
        "create",
        "show",
        "alter",
        "drop"
      ],
      "label": "Paimon Database",
      "description": "Paimon Database"
    },
    {
      "itemId": 3,
      "name": "table",
      "type": "string",
      "level": 30,
      "parent": "database",
      "mandatory": true,
      "isValidLeaf": true,
      "lookupSupported": true,
      "recursiveSupported": false,
      "excludesSupported": true,
      "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher",
      "matcherOptions": {
        "wildCard": true,
        "ignoreCase": true
      },
      "validationRegEx": "",
      "validationMessage": "",
      "uiHint": "",
      "accessTypeRestrictions": [
        "create",
        "show",
        "alter",
        "drop",
        "insert",
        "select"
      ],
      "label": "Paimon Table",
      "description": "Paimon Table"
    },
    {
      "itemId": 4,
      "name": "column",
      "type": "string",
      "level": 40,
      "parent": "table",
      "mandatory": true,
      "lookupSupported": true,
      "recursiveSupported": false,
      "excludesSupported": true,
      "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher",
      "matcherOptions": {
        "wildCard": true,
        "ignoreCase": true
      },
      "validationRegEx": "",
      "validationMessage": "",
      "uiHint": "",
      "accessTypeRestrictions": [
        "select"
      ],
      "label": "Paimon Column",
      "description": "Paimon Column"
    }
  ],//需要进行校验的访问类型
  "accessTypes": [
    {
      "itemId": 1,
      "name": "show",
      "label": "Show"
    },
    {
      "itemId": 2,
      "name": "insert",
      "label": "Insert"
    },
    {
      "itemId": 3,
      "name": "alter",
      "label": "Alter"
    },
    {
      "itemId": 4,
      "name": "create",
      "label": "Create"
    },
    {
      "itemId": 5,
      "name": "drop",
      "label": "Drop"
    },
    {
      "itemId": 6,
      "name": "select",
      "label": "Select"
    },
    {
      "itemId": 7,
      "name": "all",
      "label": "All",
      "impliedGrants":
      [
        "select",
        "insert",
        "create",
        "drop",
        "alter",
        "show"
      ]
    }
  ],
   // ranger serviceUI上需要填写的配置,RangerBaseService的validateConfig方法就是对这些配置进行校验
  "configs": [
    {
      "itemId": 1,
      "name": "username",
      "type": "string",
      "mandatory": true,
      "validationRegEx": "",
      "validationMessage": "",
      "uiHint": "",
      "label": "Username"
    },
    {
      "itemId": 2,
      "name": "password",
      "type": "password",
      "mandatory": false,
      "validationRegEx": "",
      "validationMessage": "",
      "uiHint": "",
      "label": "Password"
    },
    {
      "itemId": 3,
      "name": "jdbc.driver_class",
      "type": "string",
      "mandatory": true,
      "validationRegEx": "",
      "validationMessage": "",
      "uiHint": "",
      "defaultValue": "com.mysql.cj.jdbc.Driver"
    },
    {
      "itemId": 4,
      "name": "jdbc.url",
      "type": "string",
      "mandatory": true,
      "defaultValue": "",
      "validationRegEx": "",
      "validationMessage": "",
      "uiHint": ""
    }
  ],
  "enums": [
  ],
  "contextEnrichers": [
  ],
  "policyConditions":
  [
  ],//对数据某些字段进行脱敏使用
  "dataMaskDef": {
    "accessTypes": [
      {
        "name": "select"
      }
    ],
    "resources": [
      {
        "name": "catalog",
        "matcherOptions": {
          "wildCard": "true"
        },
        "lookupSupported": true,
        "uiHint":"{ \"singleValue\":true }"
      },
      {
        "name": "database",
        "matcherOptions": {
          "wildCard": "true"
        },
        "lookupSupported": true,
        "uiHint":"{ \"singleValue\":true }"
      },
      {
        "name": "table",
        "matcherOptions": {
          "wildCard": "true"
        },
        "lookupSupported": true,
        "uiHint":"{ \"singleValue\":true }"
      },
      {
        "name": "column",
        "matcherOptions": {
          "wildCard": "true"
        },
        "lookupSupported": true,
        "uiHint":"{ \"singleValue\":true }"
      }
    ],//脱敏的函数
    "maskTypes": [
      {
        "itemId": 1,
        "name": "MASK",
        "label": "Redact",
        "description": "Replace lowercase with 'x', uppercase with 'X', digits with '0'",
        "transformer": "mask({col})",
        "dataMaskOptions": {
        }
      },
      {
        "itemId": 2,
        "name": "MASK_SHOW_LAST_4",
        "label": "Partial mask: show last 4",
        "description": "Show last 4 characters; replace rest with 'x'",
        "transformer": "mask_show_last_n({col}, 4, 'x', 'x', 'x', -1, '1')"
      },
      {
        "itemId": 3,
        "name": "MASK_SHOW_FIRST_4",
        "label": "Partial mask: show first 4",
        "description": "Show first 4 characters; replace rest with 'x'",
        "transformer": "mask_show_first_n({col}, 4, 'x', 'x', 'x', -1, '1')"
      },
      {
        "itemId": 4,
        "name": "MASK_HASH",
        "label": "Hash",
        "description": "Hash the value",
        "transformer": "mask_hash({col})"
      },
      {
        "itemId": 5,
        "name": "MASK_NULL",
        "label": "Nullify",
        "description": "Replace with NULL"
      },
      {
        "itemId": 6,
        "name": "MASK_NONE",
        "label": "Unmasked (retain original value)",
        "description": "No masking"
      },
      {
        "itemId": 12,
        "name": "MASK_DATE_SHOW_YEAR",
        "label": "Date: show only year",
        "description": "Date: show only year",
        "transformer": "mask({col}, 'x', 'x', 'x', -1, '1', 1, 0, -1)"
      },
      {
        "itemId": 13,
        "name": "CUSTOM",
        "label": "Custom",
        "description": "Custom"
      }
    ]
  },//对数据行级过滤
  "rowFilterDef": {
    "accessTypes": [
      {
        "name": "select"
      }
    ],
    "resources": [
      {
        "name": "catalog",
        "matcherOptions": {
          "wildCard": "true"
        },
        "lookupSupported": true,
        "mandatory": true,
        "uiHint": "{ \"singleValue\":true }"
      },
      {
        "name": "database",
        "matcherOptions": {
          "wildCard": "true"
        },
        "lookupSupported": true,
        "mandatory": true,
        "uiHint": "{ \"singleValue\":true }"
      },
      {
        "name": "table",
        "matcherOptions": {
          "wildCard": "true"
        },
        "lookupSupported": true,
        "mandatory": true,
        "uiHint": "{ \"singleValue\":true }"
      }
    ]
  }
}

说白了就是定义一些资源,以及对这些资源进行校验的访问类型。例如table资源支持的访问类型是"create","show", "alter","drop","insert","select"。

这里简单说一下dataMaskDef,例如用户查询时想对某些字段进行脱密,他的查询sql是:SELECT NAME,PHONE FROM USER;对phone字段想做脱敏的话可以将查询修改为:SELECT NAME,CAST(mask(PHONE) AS STRING) FROM USER。实现这个功能就需要平台侧拿到sql之后对sql进行解析得到表的字段,之后访问Ranger判断这个字段需不需要进行datamask,需要的或就将其转换成对应的函数。

https://juejin.cn/post/7231858374827933753这篇文章讲的更细致一些,可以参考。

注意:range只是用做记录哪些表的字段需要做datamask,具体解析sql之类的需要平台自己去做,可以将Ranger当做一个记录了权限相关信息的数据库来看。

Ranger Plugin

rangerPlugin会定时从service load相关policy到本地做鉴权。需要定义三个xml文件。需要将以下三个文件放在resources文件下,或者放在java启动时classpath下:

ranger-paimon-dev-audit.xml

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
</configuration>

ranger-paimon-dev-policymgr-ssl.xml

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration xmlns:xi="http://www.w3.org/2001/XInclude">
    <!--  The following properties are used for 2-way SSL client server validation -->
    <property>
        <name>xasecure.policymgr.clientssl.keystore</name>
        <value>hadoopdev-clientcert.jks</value>
        <description>
            Java Keystore files
        </description>
    </property>
    <property>
        <name>xasecure.policymgr.clientssl.truststore</name>
        <value>cacerts-xasecure.jks</value>
        <description>
            java truststore file
        </description>
    </property>
    <!--路径自己指定一个-->
    <property>
        <name>xasecure.policymgr.clientssl.keystore.credential.file</name>
        <value>jceks://file/User/xxxx/work/keystore-hadoopdev-ssl.jceks</value>
    </property>

    <!--路径自己指定一个-->
    <property>
        <name>xasecure.policymgr.clientssl.truststore.credential.file</name>
        <value>jceks://file/User/xxx/work/truststore-hadoopdev-ssl.jceks</value>
    </property>
</configuration>

ranger-paimon-dev-security.xml

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration xmlns:xi="http://www.w3.org/2001/XInclude">
<!--此处填写测试环境创建的ranger service name-->
    <property>
        <name>ranger.plugin.paimon-dev.service.name</name>
        <value>paimonrt</value>
    </property>

    <property>
        <name>ranger.plugin.paimon-dev.policy.source.impl</name>
        <value>org.apache.ranger.admin.client.RangerAdminRESTClient</value>
    </property>
<!--此处填写测试环境的ranger admin url 不要写ip 如果有kerberos认证-->
    <property>
        <name>ranger.plugin.paimon-dev.policy.rest.url</name>
        <value>xxxxxx</value>
    </property>

    <property>
        <name>ranger.plugin.paimon-dev.policy.pollIntervalMs</name>
        <value>30000</value>
        <description>
            How often to poll for changes in policies?
        </description>
    </property>
    <property>
        <name>ranger.plugin.paimon-dev.policy.rest.ssl.config.file</name>
        <value>ranger-paimon-policymgr-ssl.xml</value>
        <description>
            Path to the file containing SSL details to contact Ranger Admin
        </description>
    </property>
<!--cache路径自己指定一个-->
    <property>
        <name>ranger.plugin.paimon-dev.policy.cache.dir</name>
        <value>/Users/xxxx/work/cache</value>
        <description>
            Directory where Ranger policies are cached after successful retrieval from the source
        </description>
    </property>
</configuration>

实现Plugin以及做验证,此处checkPermission()方法更详细的实现可以看,贴出的代码只是做个示例。

https://github.com/apache/ranger/compare/master...herefree:ranger:support-paimon-ranger

public class RangerPaimonPlugin extends RangerBasePlugin {

    public RangerPaimonPlugin(String serviceType) {
        super(serviceType, null, null);
        super.init();
    }

    public RangerPaimonPlugin(String serviceType, String serviceName) {
        super(serviceType, serviceName, null);
        super.init();
    }

}

public boolean checkPermission(AccessType accessType, PrivilegedEntity entity, UserGroupInformation ugi) {
          RangerPaimonPlugin plugin = new RangerPaimonPlugin('xxxx');
          RangerAccessRequestImpl request  = new RangerAccessRequestImpl();
          RangerResourceImpl      resource = new RangerResourceImpl();
          resource.setValue("queue", entity.getName());
          request.setResource(resource);
          request.setAccessType(getRangerAccessType(accessType));
          request.setUser(ugi.getShortUserName());
           request.setUserGroups(Sets.newHashSet(ugi.getGroupNames()));
           request.setAccessTime(new Date());
           request.setClientIPAddress(getRemoteIp());
          RangerAccessResult result = plugin.isAccessAllowed(request);
          return result == null ? false : result.getIsAllowed();
     }

Ranger Api

用户可以使用Api方式来对policy进行增删改查,当前也可以在rangerServiceUI上进行操作,这里记录下如何使用api方式创建policy。ranger官方api文档https://cwiki.apache.org/confluence/display/RANGER/Ranger+Client+Libraries

public class PaimonPolicyManager {
    private RangerClient rangerClient;

    private String policyName;
    private String RANGER_SERVICE_NAME;
    public void createPolicy() {
       //先通过api查找是否存在对应的policy
        Map<String, String> filter = new HashMap<>();
        filter.put("policyName", policyName);
        filter.put("serviceName", RANGER_SERVICE_NAME);
        List<RangerPolicy> policies = rangerClient.findPolicies(filter);
        if (policies.isEmpty()) {
            //不存在就创建新的policy
            rangerClient.createPolicy(creatPolicy("group", policyName, "db", "tb", Collections.singletonList("select")));
        }else{
            //存在的话就更新,在原有的policy上新增一个Policyitem(例如原来有select权限,后面在新增个drop权限)
            List<String> updatePermission = new ArrayList<>();
            if(checkPolicy(policies.get(0),"group","select")) {
                RangerPolicy rangerPolicy = addPolicyIterm(policies.get(0), "group", Collections.singletonList("select"));
                rangerClient.updatePolicy(RANGER_SERVICE_NAME,policyName,rangerPolicy);
            }
        }
    }

    public boolean checkPolicy(RangerPolicy rangerPolicy, String group, String permissionOp) {
        for (RangerPolicy.RangerPolicyItem policyItem : rangerPolicy.getPolicyItems()) {
            List<String> groups = policyItem.getGroups();
            if (!groups.contains(group)) {
                continue;
            }
            for (RangerPolicy.RangerPolicyItemAccess access : policyItem.getAccesses()) {
                if (access.getType().equals(permissionOp)) {
                    return true;
                }
            }
        }
        return false;
    }

    public RangerPolicy createPolicy(String group, String policyName, String dbname, String tbName, List<String> permissionOpsList) {
        RangerPolicy rangerPolicy = new RangerPolicy();
        rangerPolicy.setService(RANGER_SERVICE_NAME);
        rangerPolicy.setName(policyName);
        rangerPolicy.setResources(creatResource(dbname,tbName));
        List<RangerPolicy.RangerPolicyItem> rangerPolicyItemList = new ArrayList<>();
        for(String op:permissionOpsList) {
            RangerPolicy.RangerPolicyItem rangerPolicyItem = creatRangerPolicyItem(group, op);
            rangerPolicyItemList.add(rangerPolicyItem);
        }
        rangerPolicy.setPolicyItems(rangerPolicyItemList);
        return rangerPolicy;
    }

    public static Map<String, RangerPolicy.RangerPolicyResource> createResource(String dbName, String tbName) {

        Map<String, RangerPolicy.RangerPolicyResource> resourceMap = new HashMap<>();
        resourceMap.put("catalog", new RangerPolicy.RangerPolicyResource("paimon"));
        resourceMap.put("database", new RangerPolicy.RangerPolicyResource(dbName));
        resourceMap.put("table", new RangerPolicy.RangerPolicyResource(tbName));

        return resourceMap;
    }

    public RangerPolicy.RangerPolicyItem createRangerPolicyItem(String group, String permission) {

        RangerPolicy.RangerPolicyItem rangerPolicyItem = new RangerPolicy.RangerPolicyItem();
        rangerPolicyItem.setGroups(Collections.singletonList(group));
        RangerPolicy.RangerPolicyItemAccess rangerPolicyItemAccess = new RangerPolicy.RangerPolicyItemAccess();
        rangerPolicyItemAccess.setType(permission);
        rangerPolicyItemAccess.setIsAllowed(true);
        rangerPolicyItem.setAccesses(Collections.singletonList(rangerPolicyItemAccess));

        return rangerPolicyItem;
    }

    private RangerPolicy addPolicyIterm(RangerPolicy rangerPolicy, String group, List<String> permissionOpList) {

        List<RangerPolicy.RangerPolicyItem> addRangerPolicyItemList = new ArrayList<>();
        for (String permissionOp : permissionOpList) {
            addRangerPolicyItemList.add(createRangerPolicyItem(group, permissionOp));
        }
        List<RangerPolicy.RangerPolicyItem> policyItems = rangerPolicy.getPolicyItems();
        policyItems.addAll(addRangerPolicyItemList);

        return rangerPolicy;
    }

}

标签: none

评论已关闭