ACL(Acess Control List),ZooKeeper作为一个分布式协调框架,其内部存储的都是都是一些关乎分布式系统运行状态的元数据,尤其是设计到一些分布式锁、Master选举和协调等应用场景。我们需要有效地保障ZooKeeper中的数据安全,幸好,Zookeeper也提供了一套完善的ACL权限控制机制来保障数据的安全。
1. 概述
传统的文件系统中,ACL分为两个维度,一个是属组,一个是权限,子目录/文件默认继承父目录的ACL。而在Zookeeper中,node的ACL是没有继承关系的,是独立控制的。Zookeeper的ACL,可以从三个维度来理解:一是scheme; 二是user; 三是permission,通常表示为scheme:id:permissions, 下面从这三个方面分别来介绍:
(1)scheme: scheme对应于采用哪种方案来进行权限管理,zookeeper实现了一个pluggable的ACL方案,可以通过扩展scheme,来扩展ACL的机制。zookeeper-3.4.4缺省支持下面几种scheme:
-
- world: 它下面只有一个id, 叫anyone, world:anyone代表任何人,zookeeper中对所有人有权限的结点就是属于world:anyone的
- auth: 它不需要id, 只要是通过authentication的user都有权限(zookeeper支持通过kerberos来进行authencation, 也支持username/password形式的authentication)
- digest: 它对应的id为username:BASE64(SHA1(password)),它需要先通过username:password形式的authentication
- ip: 它对应的id为客户机的IP地址,设置的时候可以设置一个ip段,比如ip:192.168.1.0/16, 表示匹配前16个bit的IP段
- super: 在这种scheme情况下,对应的id拥有超级权限,可以做任何事情(cdrwa)
另外,zookeeper-3.4.4的代码中还提供了对sasl的支持,不过缺省是没有开启的,需要配置才能启用。
- sasl: sasl的对应的id,是一个通过sasl authentication用户的id,zookeeper-3.4.4中的sasl authentication是通过kerberos来实现的,也就是说用户只有通过了kerberos认证,才能访问它有权限的node.
如果需要实现自己定义的Scheme,可以实现org.apache.zookeeper.server.auth.AuthenticationProvider接口。
(2)id: id与scheme是紧密相关的,具体的情况在上面介绍scheme的过程都已介绍,这里不再赘述。
(3)permission: zookeeper目前支持下面一些权限:
- CREATE(c): 创建权限,可以在在当前node下创建child node
- DELETE(d): 删除权限,可以删除当前的node
- READ(r): 读权限,可以获取当前node的数据,可以list当前node所有的child nodes
- WRITE(w): 写权限,可以向当前node写数据
- ADMIN(a): 管理权限,可以设置当前node的permission
简单来说,zookeeper的这5种操作权限,CREATE、READ、WRITE、DELETE、ADMIN 也就是 增、删、改、查、管理权限,这5种权限简写为crwda(即:每个单词的首字符缩写)
注:这5种权限中,delete是指对子节点的删除权限,其它4种权限指对自身节点的操作权限
下面我们测试一下IP和Digest:
首先是IP:
@Test
public void testAclServer() throws Exception{
CountDownLatch cdl = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper("192.168.1.111:2181,192.168.1.112:2181,192.168.1.113:2181", 300000, new Watcher() {
// 监控所有被触发的事件
public void process(WatchedEvent event) {
KeeperState keeperState = event.getState();
EventType eventType = event.getType();
String path = event.getPath();
if(KeeperState.SyncConnected.equals(keeperState)){
if(EventType.None.equals(eventType)){
System.out.println("zk服务连接成功");
cdl.countDown();
}else if(EventType.NodeCreated.equals(eventType)){
System.out.println("创建节点:" + path);
}
}
}
});
cdl.await();
/**
* ip:通过ip地址粒度来进行控制权限。例如配置了:ip:192.168.1.107即表示权限控制都是针对这个ip地址的,也就是说只有这个ip地址有权限。同时也支持按网段分配,比如192.168.1.*。
* 就是不需要这种格式"user:password"
*/
if(zk.exists("/testAclServer0", true) == null){
Id id = new Id("ip","192.168.1.122");
ACL acl = new ACL(Perms.ALL,id);
zk.create("/testAclServer0", "testAclServer0".getBytes(), Collections.singletonList(acl), CreateMode.PERSISTENT);
}
zk.close();
}
注意:在创建完后不能继续写操作此节点的代码,因为本次连接是没有认证的。
到后台看一下ACL:
接下来我们尝试一下获取这个节点的数据,我们记得
1、在Java连接客户端后要添加权限zk.addAuthInfo
2、Linux使用zkCli.sh命令登录后也要添加权限addauth ip 192.168.1.122:cdrwa(因为所有操作都要权限,所以是cdrwa)
@Test
public void testAclServerClient() throws Exception{
CountDownLatch cdl = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper("192.168.1.111:2181,192.168.1.112:2181,192.168.1.113:2181", 300000, new Watcher() {
// 监控所有被触发的事件
public void process(WatchedEvent event) {
KeeperState keeperState = event.getState();
EventType eventType = event.getType();
String path = event.getPath();
if(KeeperState.SyncConnected.equals(keeperState)){
if(EventType.None.equals(eventType)){
System.out.println("zk服务连接成功");
cdl.countDown();
}else if(EventType.NodeCreated.equals(eventType)){
System.out.println("创建节点:" + path);
}
}
}
});
cdl.await();
/**
* 不设置权限会获取失败:KeeperErrorCode = NoAuth for /testAclServer\
* 同样,在Linux用zkCli.sh登录客户端后,如果不设置权限(addauth ip 192.168.1.122:cdrwa ),也是不能操作节点,会报错:Authentication is not valid : /testAclServer
*/
zk.addAuthInfo("ip", "192.168.1.127".getBytes());
try {
System.out.println("获取节点信息:"+new String(zk.getData("/testAclServer0",false, null)));
zk.delete("/testAclServer0", -1);
System.out.println("删除节点信息:");
} catch (Exception e) {
System.out.println("获取节点信息失败:"+e.getMessage());
}
zk.close();
}
接下来是测试Gigest:
我发现有两种方法
第一种是像上面的那样,在acls那里做文章。
@Test
public void testAclServer2() throws Exception{
CountDownLatch cdl = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper("192.168.1.111:2181,192.168.1.112:2181,192.168.1.113:2181", 300000, new Watcher() {
// 监控所有被触发的事件
public void process(WatchedEvent event) {
KeeperState keeperState = event.getState();
EventType eventType = event.getType();
String path = event.getPath();
if(KeeperState.SyncConnected.equals(keeperState)){
if(EventType.None.equals(eventType)){
System.out.println("zk服务连接成功");
cdl.countDown();
}else if(EventType.NodeCreated.equals(eventType)){
System.out.println("创建节点:" + path);
}
}
}
});
cdl.await();
/**
* 使用用户名/密码的方式验证,采用username:BASE64(SHA1(password))的字符串作为ACL的ID
*/
if(zk.exists("/testAclServer2", true) == null){
//如果acls是放全部的,就是int ALL = READ | WRITE | CREATE | DELETE | ADMIN 都要权限控制
Id id = new Id("digest","123456");
ACL acl = new ACL(Perms.ALL,id);
zk.create("/testAclServe2", "testAclServer2".getBytes(), Collections.singletonList(acl), CreateMode.PERSISTENT);
}
zk.close();
}
我们到后台看一下ACL:
第二种是在连接zk服务后,接着就添加权限控制:
我们发现,这种方法的话,就算不是username:password也是可以的。但是上面的方法如果不是username:password格式是行不通的,会报错:org.apache.zookeeper.KeeperException$InvalidACLException: KeeperErrorCode = InvalidACL for /testAclServer2
@Test
public void testAclServer3() throws Exception{
CountDownLatch cdl = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper("192.168.1.111:2181,192.168.1.112:2181,192.168.1.113:2181", 300000, new Watcher() {
// 监控所有被触发的事件
public void process(WatchedEvent event) {
KeeperState keeperState = event.getState();
EventType eventType = event.getType();
String path = event.getPath();
if(KeeperState.SyncConnected.equals(keeperState)){
if(EventType.None.equals(eventType)){
System.out.println("zk服务连接成功");
cdl.countDown();
}else if(EventType.NodeCreated.equals(eventType)){
System.out.println("创建节点:" + path);
}
}
}
});
cdl.await();
zk.addAuthInfo("digest","123456".getBytes());
if(zk.exists("/testAclServer3", true) == null){
//如果acls是放全部的,就是int ALL = READ | WRITE | CREATE | DELETE | ADMIN 都要权限控制
/*Id id = new Id("digest","123456");//org.apache.zookeeper.KeeperException$InvalidACLException: KeeperErrorCode = InvalidACL for /testAclServer2
ACL acl = new ACL(Perms.ALL,id);*/
zk.create("/testAclServer3", "testAclServer3".getBytes(), Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
}
zk.close();
}
我们先到后台看一下ACL:
我们可以发现,它还是username:password这种格式。
但是呢,我们添加权限信息呢,还是直接添加123456即可操作节点。
1、在Java连接客户端后添加权限zk.addAuthInfo("digest","123456".getBytes())
2、Linux使用zkCli.sh命令登录后也要添加权限addauth digest 123456
这是非常的疑惑的,我们看一下源码Ids.CREATOR_ALL_ACL是啥回事。
我们再看回上面介绍的auth:它不需要id, 只要是通过authentication的user都有权限(zookeeper支持通过kerberos来进行authencation, 也支持username/password形式的authentication)
那么到这里我们就清楚了。