初窥ZooKeeper的ACL权限控制

        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)

那么到这里我们就清楚了。

猜你喜欢

转载自blog.csdn.net/Howinfun/article/details/82800016