Zookeeper权限管理之坑

权限介绍

开始之前先介绍一些Zookeeper的权限。zookeeper支持的权限有5种分别是

  • CREATE: 你可以创建子节点。
  • READ: 你可以获取节点数据以及当前节点的子节点列表。
  • WRITE: 你可以为节点设置数据。
  • DELETE: 你可以删除子节点。
  • ADMIN: 可以为节点设置权限。

接下来看看授权所需要的语法

[zk: localhost:2181(CONNECTED) 0] create /zoo 16
Created /zoo

[zk: localhost:2181(CONNECTED) 1] setAcl /zoo  rw
rw does not have the form scheme:id:perm
Acl is not valid : /zoo

看到了授权需要的模式是 scheme:id:perm,额,不得不说这里说id也是坑爹的。具体来说应该是用expression或者 id, 下面说一下scheme

scheme介绍

  • world 表示所有。创建节点的默认权限。有唯一的id是anyone授权的时候的模式为 world:anyone:rwadc 表示所有人都对这个节点有rwadc的权限。这里用的是id而不是expression

  • auth 不需要id。不过这里应该用expression来表示。即(scheme:expression:perm)

  • digest 使用用户名:密码编码成md5的方式来作为访问控制列表的id。但是这里id不作为授权语句的一部分,这里也是用expression的方式。用户名: 密码先进行sha1编码后再用base64编码。这个比较恶心,后面再详细介绍。

  • host 使用用户主机名作为访问控制列表的id。但是这里需要注意的是表达式用的是主机名的后缀即可。举个例子。如果表达式设置为 corp.com可以匹配如host1.corp.com, host2.corp.com的主机名,但是不能匹配 host1.zookeeper.com这个主机名。

  • ip 跟主机名类似,这里用客户端的ip地址作为访问控制列表的id。表达式可以用 addr/bits这种方式来设置ip白名单。

OK, 以上只是官方文档的粗略翻译,以及加上自己的一些理解所组织起来的语言。

下面对授权方式作些解释,这里只是讲authdigest两种方式。

auth的授权方式

创建新的节点
[zk: localhost:2181(CONNECTED) 7] create /auth auth
Created /auth
授予auth权限,这里就有个坑,文档里面根本没有说明应该如何授予这个权限。
auth doesn't use any id, represents any authenticated user.

要他何用?也就是说文档没有说明该用何种expression来表示这个权限,后面经过翻阅资料查得,username:password的来作为expression。所以有了下面的的语法。

[zk: localhost:2181(CONNECTED) 9] setAcl /auth auth:zookeeper:zookeeper:rwadc
Acl is not valid : /auth
从上面可以看到这里Acl不能识别。这里又是一个文档的不足的地方,我是后来翻阅博客才知道有下面这样的黑魔法
  • 设置这个权限之前首先需要
[zk: localhost:2181(CONNECTED) 12] addauth digest zookeeper:zookeeper
  • 然后再设置auth
[zk: localhost:2181(CONNECTED) 13] setAcl /auth auth:zookeeper:zookeeper:rwadc
cZxid = 0x3
ctime = Mon May 30 10:32:15 CST 2016
mZxid = 0x3
mtime = Mon May 30 10:32:15 CST 2016
pZxid = 0x3
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
  • 检查权限
[zk: localhost:2181(CONNECTED) 14] getAcl /auth
'digest,'zookeeper:4lvlzsipXVaEhXMd+2qMrLc0at8=
: cdrwa
  • 可以看到权限已经设置成功了。
我也不知道怎么吐槽官方文档好了。冷暖自知。(T_T)

digest授权方式

这种方式跟auth的方式是差不多的,不同的在于

[zk: localhost:2181(CONNECTED) 2] create /digest digest
Created /digest
[zk: localhost:2181(CONNECTED) 3] setAcl /digest digest:zookeeper:zookeeper:rwadc
cZxid = 0x15
ctime = Mon May 30 10:59:24 CST 2016
mZxid = 0x15
mtime = Mon May 30 10:59:24 CST 2016
pZxid = 0x15
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 6
numChildren = 0
[zk: localhost:2181(CONNECTED) 5] getAcl /digest
'digest,'zookeeper:zookeeper
: cdrwa

可以看出比较明显的:

  1. 设置之前不需要通过addauth添加对应的授权认证,而是直接授权便可。
  2. 命令行设置了什么,然后在获取对应的权限的时候就会把对应的字符串显式记录下来,而不会进行任何编码, 如上面的zookeeper:zookeeper。这种方式就需要自己去编码了。
  3. 这也是个比较坑爹的事情,设置密码之前需要把密码先自己进行sha1编码然后吧结果进行base64编码。如果编码的方式错误你将永远失去这个节点的访问权。

首先对想要的用户名:密码进行处理

 # 正确的方式
 >> echo -n zookeeper:zookeeper | openssl dgst -binary -sha1 | openssl base64
 >> 4lvlzsipXVaEhXMd+2qMrLc0at8=
 
 # 错误的方式
 >> echo zookeeper:zookeeper | openssl dgst -binary -sha1 | openssl base64
 >> VWUr1lLcb0QrQUJmK+WEKUYYnV4=
 
 >> echo -n zookeeper:zookeeper | sha1sum |base64
 >> ZTI1YmU1Y2VjOGE5NWQ1Njg0ODU3MzFkZmI2YThjYWNiNzM0NmFkZiAgLQo=

上面是我踩过的坑。

得出的结果可见,跟用auth方式添加权限之后getAcl得到的密码结果是一样的。然后再把这个结果作为密码用digest的方式存进去

[zk: localhost:2181(CONNECTED) 18] setAcl /digest digest:zookeeper:4lvlzsipXVaEhXMd+2qMrLc0at8=:rwdca
cZxid = 0x1d
ctime = Mon May 30 11:12:54 CST 2016
mZxid = 0x1d
mtime = Mon May 30 11:12:54 CST 2016
pZxid = 0x1d
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 6
numChildren = 0

就可以通过addauth的方式访问了

[zk: localhost:2181(CONNECTED) 0] get /digest
Authentication is not valid : /digest
[zk: localhost:2181(CONNECTED) 1] addauth digest zookeeper:zookeeper
[zk: localhost:2181(CONNECTED) 2] get /digest
digest
cZxid = 0x1d
ctime = Mon May 30 11:12:54 CST 2016
mZxid = 0x1d
mtime = Mon May 30 11:12:54 CST 2016
pZxid = 0x1d
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 6
numChildren = 0

忘记了密码?

如果忘记了该子节点的授权用户名还有密码。这里是比较蛋疼的事情。比如

[zk: localhost:2181(CONNECTED) 10] setAcl /forget digest:because:because:rwacd
cZxid = 0x2f
ctime = Mon May 30 11:29:55 CST 2016
mZxid = 0x30
mtime = Mon May 30 11:30:16 CST 2016
pZxid = 0x2f
cversion = 0
dataVersion = 1
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 28
numChildren = 0
[zk: localhost:2181(CONNECTED) 11] get /forget
Authentication is not valid : /forget

由于我们基本上找不到because在base64反编码后再sha1反编码后的样子,所以基本上这个节点的控制权可以说是失去了。

解决方案

  1. 由于我们有/目录的权限,所以我们还是可以通过delete来删除这个子节点,重新设置。
  2. 如果我们是服务的维护者,通过设置配置文件skipACL=yes然后重启服务。所有操作就可以逃过ACL的检测。不过这种方式比较危险,最好是限制只有本地可以访问的时候进行。防止其他用户可以直接操作其他节点。不过设置好之后别忘了要重新开启ACL。

推荐阅读更多精彩内容