ZooKeeper系统模型之ACL。
ZooKeeper的ACL权限控制和Unix/Linux操作系统的ACL有一些区别,读者可以从三个方面来理解ACL机制,分别是:权限控制(Scheme)、授权对象(ID)和权限(Permission),通常使用“scheme:id:permission”来标识一个有效的ACL信息。
权限模式:Scheme
权限模式用来确定权限验证中使用校验策略。在ZooKeeper中,开发人员使用最多的就是以下四种权限模式。
IP
IP模式通过IP地址粒度来进行权限控制,例如配置了“ip:192.168.0.110”,即表示权限控制都是针对这个IP地址的。同时,IP模式也支持按照网段的方式进行配置,例如“ip:192.168.0.1/24”表示针对192.168.0.*这个IP段进行权限控制。
Digest
Digest是最常用的权限控制模式,也更符合我们对于权限控制的认识,其以类似于“username:password”形式的权限标识来进行权限配置,便于区分不同应用来进行权限控制。
当我们通过“username:password”形式配置了权限标识后,ZooKeeper会对其先后进行两次编码处理,分别是SHA-1算法加密和BASE64编码,其具体实现由DigestAuthenticationProvider.generateDigest(String idPassword)函数进行封装,下面代码所示为使用该函数进行“username:password”编码的一个实例。
/**
* 对“username:password”进行编码
*
*/
public class DigestAuthenticationProviderUsage {
public static void main(String[] args) {
try {
System.out.println(DigestAuthenticationProvider.generateDigest("foo:zk-book"));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}运行程序,输出结果如下:
从上面的运行结果中可以看出,“username:password”最终会被混淆为一个无法辨识的字符串。
World
World是一种最开放的权限控制模式,从其名字中也可以看出,事实上这种权限控制方式几乎没有任何作用,数据节点的访问权限对所有用户开放,即所有用户都可以在不进行任何权限校验的情况下操作ZooKeeper上的数据。另外,World模式也可以看作是一种特殊的Digest模式,他只有一个权限标识,即“world:anyone”。
Super
Super模式,顾名思义就是超级用户的意思,也是一种特殊的Digest模式。在Super模式下,超级用户可以对任意ZooKeeper上的数据节点进行任何操作。
授权对象:ID
授权对象指的是权限赋予的用户或一个指定实体,例如IP地址或是机器等。在不同的权限模式下,授权对象是不同的,下表中列出了各个权限模式和授权对象之间的对应关系。
权限模式 授权对象 IP 通常是一个IP地址或是IP段,例如“192.168.0.110”或“192.168.0.1/24” Digest 自定义,通常是“username:BASE64(SHA-1(username:password))”,例如“foo:kWN6aNSbjcKWPqjiV7cg0N24raU=” World 只有一个ID:"anyone" Super 与Digest模式一致
权限:Permission
权限就是指那些通过权限检查后可以被允许执行的操作。在ZooKeeper中,所有对数据的操作权限分为以下五大类:
- CREATE(C):数据节点的创建权限,允许授权对象在该数据节点下创建子节点。
- DELETE(D):子节点的删除权限,允许授权对象删除该数据节点的子节点。
- READ(R):数据节点的读取权限,允许授权对象访问该数据节点并读取其数据内容或子节点列表等。
- WRITE(W):数据节点的更新权限,允许授权对象对该数据节点进行更新操作。
- ADMIN(A):数据节点的管理权限,允许授权对象对该数据节点进行ACL相关的设置操作。
权限扩展体系
在上文中,我们已经讲解了ZooKeeper默认提供的IP、Digest、World和Super这四种权限模式,在绝大部分的场景下,这四种权限模式已经能够很好的实现权限控制的目的。同时ZooKeeper提供了特殊的权限控制插件体系,允许开发人员通过指定方式对ZooKeeper的权限进行扩展。这些扩展的权限控制方式就像插件一样插入到ZooKeeper的权限体系中去,因此在ZooKeeper的官方文档中,也称该机制为“Pluggable ZooKeeper Authentication”。
实现自定义权限控制器
要实现自定义权限控制器非常简单,ZooKeeper定义了一个标准权限控制器需要实现的接口:org.apache.zookeeper.server.auth.AuthenticationProvider,其接口定义如下图所示。
用户可以基于该接口来进行自定义权限控制器的实现。事实上,在前面内容中提到的几个权限模式,对应的就是ZooKeeper自带的DigestAuthenticationProvider和IPAuthenticationProvider两个权限控制器。
注册自定义权限控制器
完成自定义权限控制器的开发后,接下来就需要将该权限控制器注册到ZooKeeper服务器中去了。ZooKeeper支持通过系统属性和配置文件两种方式来注册自定义的权限控制器。
- 系统属性 -Dzookeeper.authProvider.X
在ZooKeeper启动参数中配置类似于如下的系统属性:
-Dzookeeper.authProvider.1=com.zkbook.CustomAuthenticationProvider
- 配置文件方式
在zoo.cfg配置文件中配置类似于如下的配置项:
authProvider.1=com.zkbook.CustomAuthenticationProvider
对于权限控制器的注册,ZooKeeper采用了延迟加载的策略,即只有在第一次处理包含权限控制的客户端请求时,才会进行权限控制器的初始化。同时,ZooKeeper还会将所有的权限控制器都注册到ProviderRegistry中去。在具体的实现中,ZooKeeper首先会将DigestAuthenticationProvider和IPAuthenticationProvider这两个默认的控制器初始化,然后通过扫描zookeeper.authProvider.这一系统属性,获取到所有用户配置的自定义权限控制器,并完成其初始化。
ACL管理
讲解完ZooKeeper的ACL及其扩展机制后,我们来看看如何进行ACL管理。
设置ACL
通过zkCli脚本登录ZooKeeper服务器后,可以通过两种方式进行ACL的设置。一种是在数据节点创建的同时进行ACL权限的设置,命名格式如下:
- create [-s] [-e] path data acl
具体使用如下所示。
另一种方式则是使用setAcl命名单独对已经存在的数据节点进行ACL设置:
- setAcl path acl
具体使用如下所示。
Super模式的用法
根据ACL权限控制的原理,一旦对一个数据节点设置了ACL权限控制,那么其他没有被授权的ZooKeeper客户端将无法访问该数据节点,这的确很好的保证了ZooKeeper的数据安全。但同时,ACL权限控制也给ZooKeeper的运维人员带来了一个困扰:如果一个持久数据节点包含了ACL权限控制,而其创建者客户端已经退出或已不再使用,那么这些数据节点该如何清理呢?这个时候,就需要在ACL的Super模式下,使用超级管理员权限来进行处理了。要使用超级管理员权限,首先需要在ZooKeeper服务器上开启Super模式,方法是在ZooKeeper服务器启动的时候,添加如下系统属性:
- -Dzookeeper.DigestAuthenticationProvider.superDigest=foo:kWN6aNSbjcKWPqjiV7cg0N24raU=
其中,“foo”代表了一个超级管理员的用户名;“kWN6aNSbjcKWPqjiV7cg0N24raU=”是可变的,由ZooKeeper的系统管理员来进行自主配置,此例中使用的是“foo:zk-book”的编码。完成对ZooKeeper服务器的Super模式的开启后,就可以在应用程序中使用了,下面是一个使用超级管理员权限操作ZooKeeper数据节点的示例程序。
/**
* 使用Super 权限模式进行权限控制
*
*/
public class AuthSample_Super {
final static String PATH = "/zk-book";
public static void main(String[] args) throws Exception {
ZooKeeper zooKeeper1 = new ZooKeeper("127.0.0.1:2181", 5000, null);
zooKeeper1.addAuthInfo("digest", "foo:true".getBytes());
// 判断是否为空
if (zooKeeper1.exists(PATH, false) == null) {
zooKeeper1.create(PATH, "init".getBytes(), Ids.CREATOR_ALL_ACL, CreateMode.EPHEMERAL);
}
// 用管理员权限
ZooKeeper zooKeeper2 = new ZooKeeper("127.0.0.1:2181", 500000, null);
zooKeeper2.addAuthInfo("digest", "foo:zk-book".getBytes());
System.out.println(zooKeeper2.getData(PATH, false, null));
// 用其他用户访问
ZooKeeper zooKeeper3 = new ZooKeeper("127.0.0.1:2181", 50000, null);
zooKeeper3.addAuthInfo("digest", "foo:false".getBytes());
System.out.println(zooKeeper3.getData(PATH, false, null));
}
}
从上面的输出结果中,我们可以看出,由于“foo:zk-book”是一个超级管理员账户,因此能够针对一个受权限控制的数据节点zk-book随意进行操作,但是对于“foo:false”这个普通用户,就无法通过权限校验了。