美文网首页
HBase分析之simple权限验证

HBase分析之simple权限验证

作者: HZWong | 来源:发表于2017-09-13 08:58 被阅读0次

    知道了用户的机制,见HBase源码分析之用户,就可以对用户进行权限控制了,HBase提供了AccessController作为自带的认证方式,HBase称之为simple。

    1. 配置AccessController

    Simple方式的实现类是AccessController,是HBase中自带的,只要在conf/hbase-site.xml中设置好以下属性,即可生效。

    <!-- HBase Superuser -->
    <property>
      <name>hbase.superuser</name>
      <value>hbase, admin</value>
    </property>
    
    <property>
      <name>hbase.security.authentication</name>
      <value>simple</value>
    </property>
    <property>
      <name>hbase.security.authorization</name>
      <value>true</value>
    </property>
    <property>
      <name>hbase.coprocessor.master.classes</name>
      <value>org.apache.hadoop.hbase.security.access.AccessController</value>
    </property>
    <property>
      <name>hbase.coprocessor.region.classes</name>
      <value>org.apache.hadoop.hbase.security.access.AccessController</value>
    </property>
    <property>
      <name>hbase.coprocessor.regionserver.classes</name>
      <value>org.apache.hadoop.hbase.security.access.AccessController</value>
    </property>
    

    AccessController实现了CoprocessorService、AccessControlService.Interface,通过Java或者命令行执行grant、revoke操作时,会相应的调用AccessController的grant、revoke方法,方法中会将配置的权限存进hbase:acl中。

    // AccessController
    public void grant(RpcController controller,
                      AccessControlProtos.GrantRequest request,
                      RpcCallback<AccessControlProtos.GrantResponse> done) {
              ...
              AccessControlLists.addUserPermission(regionEnv.getConfiguration(), perm);
              ...
    }
    
    // AccessControlLists
    static void addUserPermission(Configuration conf, UserPermission userPerm)
        throws IOException {
      ...
      try (Connection connection = ConnectionFactory.createConnection(conf)) {
        try (Table table = connection.getTable(ACL_TABLE_NAME)) {
          table.put(p);
        }
      }
    }
    

    配置一条权限用于测试,给masa赋予表table_name的RW(读写)权限

    hbase(main):001:0> grant 'masa', 'RW', 'table_name'
    0 row(s) in 0.6260 seconds
    

    scan一下hbase:acl的表,里面已经有刚配置的记录了。然而并没有superuser的相关记录,但是superuser确实拥有所有的权限,这个问题第2节会提到。

    hbase(main):001:0> scan 'hbase:acl'
    ROW                        COLUMN+CELL                                                                 
     table_name                column=l:masa, timestamp=1505188428592, value=RW                           
    1 row(s) in 0.2620 seconds
    

    2. 验证权限

    Coprocessor提供了在各个操作之前和之后的回调,相应的可以从方法名中看出,例如:preScannerOpen、postScannerOpen。AccessController继承了Coprocessor,在操作前回调pre里会调用AccessController的permissionGranted方法来判断是否有权限执行permRequest这个Action。

    AuthResult permissionGranted(String request, User user, Action permRequest,
        RegionCoprocessorEnvironment e,
        Map<byte [], ? extends Collection<?>> families) {
      HRegionInfo hri = e.getRegion().getRegionInfo();
      TableName tableName = hri.getTable();
    
      // 如果是访问的meta region,并且是读操作,则允许
      if (hri.isMetaRegion()) {
        if (permRequest == Action.READ) {
          return AuthResult.allow(request, "All users allowed", user,
            permRequest, tableName, families);
        }
      }
      
      // 没有设置用户时,拒绝访问
      if (user == null) {
        return AuthResult.deny(request, "No user associated with request!", null,
          permRequest, tableName, families);
      }
    
      // 判断是否有这张表的permRequest权限
      if (authManager.authorize(user, tableName, (byte[])null, permRequest)) {
        return AuthResult.allow(request, "Table permission granted", user,
          permRequest, tableName, families);
      }
    
      // 判断是否有参数families的permRequest权限
      if (families != null && families.size() > 0) {
        // 所有family必须都有permRequest权限,才认为有执行permRequest的权限
        for (Map.Entry<byte [], ? extends Collection<?>> family : families.entrySet()) {
          // family是否有权限分两种情况,一种,这个family就是有权限
          if (authManager.authorize(user, tableName, family.getKey(),
              permRequest)) {
            continue;  
          }
    
          // 另一种,这个family下得所有qualifier都有权限
          if ((family.getValue() != null) && (family.getValue().size() > 0)) {
            if (family.getValue() instanceof Set) {
              Set<byte[]> familySet = (Set<byte[]>)family.getValue();
              for (byte[] qualifier : familySet) {
                if (!authManager.authorize(user, tableName, family.getKey(),
                                           qualifier, permRequest)) {
                  return AuthResult.deny(request, "Failed qualifier check", user,
                      permRequest, tableName, makeFamilyMap(family.getKey(), qualifier));
                }
              }
            } else if (family.getValue() instanceof List) {
              List<KeyValue> kvList = (List<KeyValue>)family.getValue();
              for (KeyValue kv : kvList) {
                if (!authManager.authorize(user, tableName, family.getKey(),
                        kv.getQualifier(), permRequest)) {
                  return AuthResult.deny(request, "Failed qualifier check", user,
                      permRequest, tableName, makeFamilyMap(family.getKey(), kv.getQualifier()));
                }
              }
            }
          } else {
            return AuthResult.deny(request, "Failed family check", user, permRequest,
                tableName, makeFamilyMap(family.getKey(), null));
          }
        }
    
        return AuthResult.allow(request, "All family checks passed", user, permRequest,
            tableName, families);
      }
    
      // 条件都不满足,还是deny
      return AuthResult.deny(request, "No families to check and table permission failed",
          user, permRequest, tableName, families);
    }
    

    上面这段代码调用了3次 TableAuthManager 的authorize方法,都是public boolean authorize(User user, TableName table, byte[] family, byte[] qualifier, Permission.Action action)方法参数不同的调用。在验证表权限时,调用family和qualifier传null,验证family时,qualifier传null,验证qualifier时,参数都传。

    public boolean authorize(User user, TableName table, byte[] family,
        byte[] qualifier, Permission.Action action) {
      // 认证用户是否有权限
      if (authorizeUser(user, table, family, qualifier, action)) {
        return true;
      }
    
      // 认证用户的组是否有权限
      String[] groups = user.getGroupNames();
      if (groups != null) {
        for (String group : groups) {
          if (authorizeGroup(group, table, family, qualifier, action)) {
            return true;
          }
        }
      }
      return false;
    }
    

    验证用户是否有权限,验证用户组的权限其实和用户是类似的,这里只看用户权限的认证。

    public boolean authorizeUser(User user, TableName table, byte[] family,
        byte[] qualifier, Permission.Action action) {
      if (table == null) table = AccessControlLists.ACL_TABLE_NAME;
      // 检查是否有Namespace权限
      if (authorize(user, table.getNamespaceAsString(), action)) {
        return true;
      }
      // 检查是否有表权限
      return authorize(getTablePermissions(table).getUser(user.getShortName()), table, family,
          qualifier, action);
    }
    

    Namespace权限的认证过程,认证过程第一步authorize(user, action),方法里通过globalCache判断用户是否是超级用户,这一步直接跳过了表级的验证过程,所以超级用户的权限是在hbase:acl里看不到的。第二步,从nsCache中拿到对应namespace的权限列表,认证权限。globalCache和nsCache相关内容在第三节。

    public boolean authorize(User user, String namespace, Permission.Action action) {
      // 认证用户是否是超级用户
      if (authorize(user, action)) {
        return true;
      }
      // 认证用户是否有Namespace权限,从Cache里拿到权限的列表
      PermissionCache<TablePermission> tablePerms = nsCache.get(namespace);
      if (tablePerms != null) {
        List<TablePermission> userPerms = tablePerms.getUser(user.getShortName());
        if (authorize(userPerms, namespace, action)) {
          return true;
        }
        String[] groupNames = user.getGroupNames();
        if (groupNames != null) {
          for (String group : groupNames) {
            List<TablePermission> groupPerms = tablePerms.getGroup(group);
            if (authorize(groupPerms, namespace, action)) {
              return true;
            }
          }
        }
      }
      return false;
    }
    

    Table权限的认证过程,顺序遍历Cache中拿到的权限列表,寻找匹配的权限。找到了,return true,否则return false。

    private boolean authorize(List<TablePermission> perms,
                              TableName table, byte[] family,
                              byte[] qualifier, Permission.Action action) {
      if (perms != null) {
        for (TablePermission p : perms) {
          if (p.implies(table, family, qualifier, action)) {
            return true;
          }
        }
      }
      return false;
    }
    

    单个权限的判断过程,各个值的比较,都符合返回true。

    public boolean implies(TableName table, byte[] family, byte[] qualifier,
        Action action) {
      if (!this.table.equals(table)) {
        return false;
      }
    
      if (this.family != null && (family == null || !Bytes.equals(this.family, family))) {
        return false;
      }
    
      if (this.qualifier != null && (qualifier == null || !Bytes.equals(this.qualifier, qualifier))) {
        return false;
      }
    
      // check actions
      return super.implies(action);
    }
    

    3. 权限读取和更新

    权限是从配置文件和hbase:acl表中读取出来的,都存在TableAuthManager中,分为globalCache、nsCache和tableCache。

    private TableAuthManager(ZooKeeperWatcher watcher, Configuration conf)
        throws IOException {
      this.conf = conf;
    
      // 读取globalCache
      globalCache = initGlobal(conf);
    
      this.zkperms = new ZKPermissionWatcher(watcher, this, conf);
      try {
        // 读取nsCache和tableCache,还有一部分globalCache
        this.zkperms.start();
      } catch (KeeperException ke) {
        LOG.error("ZooKeeper initialization failed", ke);
      }
    }
    

    3.1 读取globalCache

    在创建TableAuthManager的时候,调用initGlobal,从配置中读取超级用户相关的权限信息,并把启动HBase的用户加入超级用户列表,从这里就可以看到,为什么在没有配置超级用户时,启动HBase的用户就是超级用户,当然,配置了超级用户,启动HBase的用户依然是超级用户。

    private PermissionCache<Permission> initGlobal(Configuration conf) throws IOException {
      // 获取当前用户
      UserProvider userProvider = UserProvider.instantiate(conf);
      User user = userProvider.getCurrent();
      if (user == null) {
        throw new IOException("Unable to obtain the current user, " +
            "authorization checks for internal operations will not work correctly!");
      }
      PermissionCache<Permission> newCache = new PermissionCache<Permission>();
      String currentUser = user.getShortName();
    
      // 从配置中读取超级用户,并把系统当前用户加入列表
      List<String> superusers = Lists.asList(currentUser, conf.getStrings(
          Superusers.SUPERUSER_CONF_KEY, new String[0]));
      if (superusers != null) {
        for (String name : superusers) {
          // 判断是用户还是组
          if (AuthUtil.isGroupPrincipal(name)) {
            newCache.putGroup(AuthUtil.getGroupName(name),
                new Permission(Permission.Action.values()));
          } else {
            newCache.putUser(name, new Permission(Permission.Action.values()));
          }
        }
      }
      return newCache;
    }
    

    虽然看起来,在初始化超级用户列表的时候,只有配置的超级用户和系统当前用户加入了超级用户列表,实质上对hbase:acl表有读写权限的用户都会被加入超级用户列表。这个操作是在ZKPermissionWatcher的start方法中执行的,过程比较简单,最终会调用到refreshTableCacheFromWritable,这个方法在第3.2节中会详细说明。

    3.2 读取nsCache和tableCache

    在start中,首先将自己注册到watcher(ZooKeeperWatcher)中,然后读取acl的数据,写入缓存。

    public void start() throws KeeperException {
      try {
        // 向ZooKeeperWatcher注册自己
        watcher.registerListener(this);
        // 读取acl,更新缓存
        if (ZKUtil.watchAndCheckExists(watcher, aclZNode)) {
          List<ZKUtil.NodeAndData> existing =
              ZKUtil.getChildDataAndWatchForNewChildren(watcher, aclZNode);
          if (existing != null) {
            refreshNodes(existing);
          }
        }
      } finally {
        initialized.countDown();
      }
    }
    

    refreshNodes方法中会遍历acl中所有的数据节点,分别调用refreshAuthManager方法。refreshAuthManager中判断节点是Namespace还是Table,分别写入TableAuthManager的nsCache和tableCache中。

    private void refreshAuthManager(String entry, byte[] nodeData) throws IOException {
      if(AccessControlLists.isNamespaceEntry(entry)) {
        authManager.refreshNamespaceCacheFromWritable(
            AccessControlLists.fromNamespaceEntry(entry), nodeData);
      } else {
        authManager.refreshTableCacheFromWritable(TableName.valueOf(entry), nodeData);
      }
    }
    

    在更新表缓存时,判断了如果当前表是hbase:acl表,就把当前权限规则写入globalCache中,不是hbase:acl表,才会写入tableCache。这里就是之前提到的,对hbase:acl表有权限的用户,也是超级用户的源码所在。

    public void refreshTableCacheFromWritable(TableName table,   
                                     byte[] data) throws IOException {
          ...
          if (Bytes.equals(table.getName(), AccessControlLists.ACL_GLOBAL_NAME)) {
            updateGlobalCache(perms);
          } else {
            updateTableCache(table, perms);
          }
          ...
    }
    

    3.3 更新缓存

    更新缓存同步的其实是hbase:acl表中的数据,配置文件不会更新。之前提到在start中,将自己注册到watcher(ZooKeeperWatcher)中,本质是实现ZooKeeperListener的监听。ZooKeeperListener有4个方法,
    nodeCreated、nodeDataChanged、nodeChildrenChanged和nodeDataChanged。在这4个方法中都会调用refreshAuthManager来更新缓存,在3.2中也讲过,初始化时更新nsCache和tableCache用的是同一个方法。

    private void refreshAuthManager(String entry, byte[] nodeData) throws IOException {
      if(AccessControlLists.isNamespaceEntry(entry)) {
         authManager.refreshNamespaceCacheFromWritable(
              AccessControlLists.fromNamespaceEntry(entry), nodeData);
      } else {
        authManager.refreshTableCacheFromWritable(TableName.valueOf(entry), nodeData);
      }
    }
    

    -END-

    相关文章

      网友评论

          本文标题:HBase分析之simple权限验证

          本文链接:https://www.haomeiwen.com/subject/rwyhsxtx.html