构建和运维HBase集群是一个非常有挑战性的工作。HBase凭借其在海量数据的良好的扩展性和高效的读写能力,受到越来越多公司的重视。

在公司里,HBase越来越受欢迎。希望通过HBase读写数据的产品越来越多,在兴奋之余,头疼的问题也来了。毕竟,作为线上的产品,我们不希望过多人随意的访问,会照成很多潜在的风险,比如误删,误操作。但是,如果所有事情都有管理员处理,沟通的代价就会很高,而且管理员不得不处理一些Application相关的工作,导致管理低效。访问控制,就成为了一个很重要的需求。

此外,现在部署在云计算公有云的HBase集群也在日益增多,如果没有访问控制,存储其中的数据可以随意删改,对企业而言,也是无法接受的。那么HBase 的访问控制就成为了一个迫切紧急的任务。

Apache Hadoop 在0.20版本之后开始添加Kerberos认证技术。然而,直到0.21版本,这项工作仍未结束。因此,在0.22版本之前,这项特性的有效性和稳定性均不成熟。此外,Yahoo! Distribution of Hadoop 的0.20.S版本和Cloudera的第一个稳定的CDH3发布中也会加入这项安全支持。

HBase的访问控制是也是基于Kerberos的,现在,在我们的产品环境中,已经集成了Hadoop和HBase的访问控制功能。在后面的文章里,我将详细阐述HBase访问控制的具体功能和实现机制。供大家参考。

Secure HBase的特征。

1. 和Yahoo!Distribution of Hadoop 一样,基于Kerberos。在我们的环境中,我们同时部署了Secure Hadoop和Secure HBase, 关于Secure Hadoop,可以参考(Hadoop-4487)

2. 实现Coprocessor Framework, 允许HBase 管理员在Regionserver中载入定制的代码,关于Coprocessor,请参考(HBase-2001)

3. 实现了一个AccessController的Coprocessor,在代码中加入了Access Control。请参考(HBase-3025)

4. 支持基于表和column family的访问控制,不支持row级别。

认证机制

认证机制利用了Java的JAAS。 当HBase接受到透过RPC过来的请求时,将调用JAAS的doAs函数执行这个命令。具体的代码变动,可以看下面的代码比较。


1. diff --git a/src/main/java/org/apache/hadoop/hbase/ipc/HBaseServer.java b/src/main/java/org/apache/hadoop/hbase/ipc/HBaseServer.java  
2.   
3. index 40473e3..2dec6b5 100644  
4.   
5. --- a/src/main/java/org/apache/hadoop/hbase/ipc/HBaseServer.java  
6.   
7. +++ b/src/main/java/org/apache/hadoop/hbase/ipc/HBaseServer.java  
8.   
9. @@ -58,6 +58,7 @@  
10.   
11. import java.util.concurrent.ConcurrentHashMap;  
12.   
13. import java.util.concurrent.ExecutorService;  
14.   
15. import java.util.concurrent.Executors;  
16.   
17. import java.util.concurrent.LinkedBlockingQueue;  
18.   
19. +import java.security.PrivilegedExceptionAction;  
20.   
21. import org.apache.commons.logging.Log;  
22.   
23. import org.apache.commons.logging.LogFactory;  
24.   
25. @@ -1060,7 +1061,7 @@ public abstract class HBaseServer implements RpcServer {  
26.   
27. ByteArrayOutputStream buf = new ByteArrayOutputStream(buffersize);  
28.   
29. while (running) {  
30.   
31. try {  
32.   
33. -          Call call = myCallQueue.take(); // pop the queue; maybe blocked here  
34.   
35. +          final Call call = myCallQueue.take(); // pop the queue; maybe blocked here  
36.   
37. if (LOG.isDebugEnabled())  
38.   
39. LOG.debug(getName() + ": has #" + call.id + " from " +  
40.   
41. @@ -1073,7 +1074,32 @@ public abstract class HBaseServer implements RpcServer {  
42.   
43. CurCall.set(call);  
44.   
45. // TODO: simple auth -- store user in context  
46.   
47. try {  
48.   
49. -            value = call(call.connection.protocol, call.param, call.timestamp);             // make the call  
50.   
51. +            Object obj = (Writable)call.connection.ticket.doAs(new PrivilegedExceptionAction<Object>() {  
52.   
53. +                public Object run() throws IOException, InterruptedException {  
54.   
55. +                  return call(call.connection.protocol, call.param,  
56.   
57. +                              call.timestamp);  
58.   
59. +                }  
60.   
61. +              });  
62.   
63. +  
64.   
65. +            if (obj instanceof Writable) {  
66.   
67. +              value = (Writable)obj;  
68.   
69. +            }  
70.   
71. +            else {  
72.   
73. +              // doAs() return value could not be converted to Writable:  
74.   
75. +              // probably should throw an exception here in that case.  
76.   
77. +            }  
78.   
79. } catch (Throwable e) {  
80.   
81. LOG.debug(getName()+", call "+call+": error: " + e, e);  
82.   
83. errorClass = e.getClass().getName();


 

权限和授权

HBase的权限定义比较简单。比如我们不会区分是insert还是update,而统一是Put。

HBase的权限可以分为基于行和基于schema。基于行包括对表的读写。基于schema的包括了增加一列或者增加一个family,删除表之类的操作。前者是在Regionserver中运行,但是后者是在master中执行。在master中运行的不会在Coprocessor Framework中运行,但是我们上面的代码将运行在master和Regionserver中,所以不论用户是执行基于行的操作还是基于schema的操作,都可以实现访问控制。

表的所属权

所属权定义在表的TableDescriptor,对应于 .META. 中的regioninfo:owner这一栏。表的所有者有权利读写表和删除表。任何人有权利读 –ROOT— 和 .META.  , 但只有HBase 管理员修改表的所属权属性。比如,如果有一个客户端希望能读表的信息,但是你担心他会误删表的数据,就可以建立一个对某个表只读权限的用户。

Coprocessor

Coprocessor已经融合到了HBase 0.90 的版本中。如果enable了Coprocessor,RegionServer会从配置文件hbase-site.xml 中读取Coprocessor的配置。下面是Access Control coprocessor的配置片段。

<property>

<name>hbase.coprocessor.default.classes</name>

<value>org.apache.hadoop.hbase.security.rbac.AccessController</value>

</property>

Coprocessor的接口定义了一组pre- 和post- 的函数,每个对应于客户端的一个request。

比如,客户端的函数Get(),在Coprocessor中就会定义preGet()和postGet(), preX()和postX()会抛出CoprocessorException。可以定义任意数量的Coprocessor。其实这个和动态代理类似。如图,定义了三个preGet()和三个postGet(),如果遇到异常,异常将直接抛给用户终止后面的进行。


利用pre-X方法,我们可以实现访问控制。比如,如果一个客户端发送Put(T,Row)到一个Regionserver,Regionserver会首先调用用于认证的Coprocessor函数prePut检测该用户是否对该表有写权限。如果有,则写入,否则,抛出AccessDeniedException异常。

上图是preX和permission的对应关系。

吃外,我们用preX做permission 检测的同时,也使用了两个postX方法——postOpen()和postPut() 来存储Permission信息。

如何存储Permissions

表Table1的Permission会存储在三个地方。

1. Table1的第一个region在 .META.表所在的行

2. Zookeeper中/hbase/acl/table1节点

3. 所有管理table1的RegionServer在内存中的权限镜像。


.META.

我们在.META.上面加了一个colum family,acl:。对于任意一个表T, Permission信息存储在该表在.META.的第一行,存储在acl:U列中(U表示username),值可以是R,W,RW。这是Permission信息存储的原始位置,Zookeeper和regionServer的信息都是从这里拷贝的。当Secure HBase第一次启动的时候,.META. regions 首先被load到RegionServer,之后拷贝到Zookeeper和Regionserver的镜像。当权限被管理员改变,也是先修改.META. 然后到Regionserver 的mirrors。

Zookeeper

Zookeeper在.META和Regionserver镜像中起到一个桥梁的作用。在Zookeeper中,Permission 信息会存放在/hbase/acl/T, 所有含有表T  region的Regionserver都会设置watch来监视Permission信息的改变。

Regionserver的内存镜像

每一个Regionserver都会为他服务的regions维护一个内存权限镜像。镜像的内容来自于拷贝相应Zookeeper节点的Permission信息得到。Zookeeper和Regionserver的镜像之间的一致性是靠Zookeeper Watches保证的。

Zookeeper起的作用

当一个Regionserver打开一个.META. region时,AccessController:postOpen() 扫描出表在.META.中所有的第一行,建立Zookeeper 节点/hbase/acl/T,acl:columns的每行都被拷贝到Zookeeper的节点上。形成一个user=>Permission的映射表。


维护一致性: 从.META.到Zookeeper

当管理员修改了Permission信息时,实际上是对.META.的第一行出发了put操作。这会触发Coprocessor的PostPut 方法。在PostPut里,我们将修改的权限保存到对应的/hbase/acl/T节点中。


维护一致性: 从Zookeeper到Regionserver 权限镜像

下图表示两个Regionserver,每一个都有一个ZKPermissionWatcher,实现了nodeCreated()和nodeChanged()方法。对于每一个watcher 方法,改变对应的Zookeeper节点都会触发Permission镜像的刷新。


注意,Regionserver仅仅使用本地的权限镜像,而不是使用Zookeeper或者.META.来认证客户端的请求。这是处于性能的考虑。但是,如何确保每次都执行正确的认证信息?这要求Regionserver的镜像和.META.中的Permission完全一致。因为表T它的第一个region对应的.META.的那个region被打开之后才能被打开,这就保证了regionserver总是先于任何客户端之前导入Permission信息。

总结

1. META中的acl: 这个family用于存储Permission信息。Zookeeper能够自动快速地将权限的改动更新到本地的权限缓存中。

2. 一个用户的操作既不需要查询META也不需要查询Zookeeper。最新的信息能从本地缓存中获得。

3.  修改权限不需要disable表,对表权限的更新将立即并自动地通过Zookeeper从META表更新到regionserver

实例分析

现在,我们来看一个权限处理的例子

S代表处理的Regionserver, 而U代表用户。

在第三步,doAs()将HBase的系统用户转化为一个普通的U用户,类似unix的sudo su,

第五步,如果认证失败,就会返回AccessDeniedException。