Winse Blog

走走停停都是风景, 熙熙攘攘都向最好, 忙忙碌碌都为明朝, 何畏之.

Zookeeper ACL

集群又一次进行安检,SSH躲不过需要升级的,这次还加了hadoop security和zookeeper acl的bug。以前没太在意这些内容,既然安全检查出来了,还是需要处理的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ZooKeeper 未授权访问【原理扫描】
详细描述  ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。 
ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。 
在通常情况下,zookeeper允许未经授权的访问。
解决办法  为ZooKeeper配置相应的访问权限。 

方式一: 
1)增加一个认证用户 
addauth digest 用户名:密码明文 
eg. addauth digest user1:password1 
2)设置权限 
setAcl /path auth:用户名:密码明文:权限 
eg. setAcl /test auth:user1:password1:cdrwa 
3)查看Acl设置 
getAcl /path 

方式二: 
setAcl /path digest:用户名:密码密文:权限

威胁分值  5.0
危险插件  否
发现日期  2015-02-10

Zookeeper权限基本知识点、操作

Note also that an ACL pertains only to a specific znode. In particular it does not apply to children. ACL在znode上无继承性,也就是说子znode不会继承父znode的ACL权限.

  • world has a single id, anyone, that represents anyone.
  • auth doesn’t use any id, represents any authenticated user.
  • digest uses a username:password string to generate MD5 hash which is then used as an ACL ID identity. Authentication is done by sending the username:password in clear text. When used in the ACL the expression will be the username:base64 encoded SHA1 password digest.
  • ip uses the client host IP as an ACL ID identity. The ACL expression is of the form addr/bits(3.5+) where the most significant bits of addr are matched against the most significant bits of the client host IP.

zookeeper的ACL格式为 schema:id:permissions 。模式就是上面列的几种,再加一个super。创建的节点默认权限为 world:anyone:rwadc 表示所有人都对这个节点有rwadc的权限。

  • Create:允许对子节点Create 操作
  • Read:允许对本节点GetChildren 和GetData 操作
  • Write :允许对本节点SetData 操作
  • Delete :允许对子节点Delete 操作
  • Admin :允许对本节点setAcl 操作

Auth授权

不需要id,当前 “登录” 的所有users都有权限(sasl、kerberos这些授权方式不懂,囧)。虽然不需要id,但是格式还得按照 scheme:id:perm 的写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[zk: localhost:2181(CONNECTED) 15] setAcl /c auth:rwadc  
auth:rwadc does not have the form scheme:id:perm
Acl is not valid : /c

[zk: k8s(CONNECTED) 13] addauth digest a:a
[zk: k8s(CONNECTED) 14] addauth digest b:b
[zk: k8s(CONNECTED) 15] addauth digest c:c
[zk: k8s(CONNECTED) 16] create /e e
Created /e
[zk: k8s(CONNECTED) 17] setAcl /e auth::cdrwa
...省略节点输出信息

[zk: k8s(CONNECTED) 18] getAcl /e
'digest,'a:mDmPUap4qvYwm+PZOtJ/scGyHLY=
: cdrwa
'digest,'b:+F8zPn3x1CLx3qpYHEaRwIheWcc=
: cdrwa
'digest,'c:K7CO7OxIfBOQxczG+7FI9BdZ6/s=
: cdrwa

id随便写也可以,zookeeper都不记录的。

1
2
3
4
5
6
7
8
[zk: localhost:2181(CONNECTED) 9] addauth digest hdfs:hdfs    
[zk: localhost:2181(CONNECTED) 10] setAcl /c auth:x:x:rwadc
...
[zk: localhost:2181(CONNECTED) 11] getAcl /c               
'digest,'user:tpUq/4Pn5A64fVZyQ0gOJ8ZWqkY=
: cdrwa
'digest,'hdfs:0wpra2yK6RCUB9sbo0BkElpzcl8=
: cdrwa

也可以对根 / 授权,这样客户端就不能随便在根下面新建节点了。

1
2
3
4
5
6
7
8
9
[zk: localhost:2181(CONNECTED) 9] addauth digest user:password    
[zk: localhost:2181(CONNECTED) 21] setAcl / auth::rawdc

重新登录
[zk: localhost:2181(CONNECTED) 0] ls /
Authentication is not valid : /
[zk: localhost:2181(CONNECTED) 1] getAcl /
'digest,'user:tpUq/4Pn5A64fVZyQ0gOJ8ZWqkY=
: cdrwa

还原

使用有权限的用户/实例,如果都忘了那就只能放绝招:使用超级管理员登录,重新设置权限为world即可。

1
[zk: localhost:2181(CONNECTED) 26] setAcl / world:anyone:cdrwa

Digest

直接用起来比 auth 简单,直接把密文交给zookeeper。首先得生成对应用户的密码。

1
2
3
4
5
[root@k8s zookeeper-3.4.10]# java -cp zookeeper-3.4.10.jar:lib/* org.apache.zookeeper.server.auth.DigestAuthenticationProvider user:password
user:password->user:tpUq/4Pn5A64fVZyQ0gOJ8ZWqkY=

[root@k8s zookeeper-3.4.10]# java -cp zookeeper-3.4.10.jar:lib/* org.apache.zookeeper.server.auth.DigestAuthenticationProvider es:es
es:es->es:KiHfMOSWCTgPKpz78IL/6qO8AEE=

scheme是digest的时候,id需要密文。通过Zookeeper的客户端编码方式添加认证(登录),digest对应的auth数据是明文。

ACL授权一样使用 setAcl :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$$ A实例
[zk: localhost:2181(CONNECTED) 17] setAcl /b digest:user:tpUq/4Pn5A64fVZyQ0gOJ8ZWqkY=:cdrwa
和md5密码类似,数据库被盗了,如果是常用的密码会被猜出来
[zk: localhost:2181(CONNECTED) 18] getAcl /b
'digest,'user:tpUq/4Pn5A64fVZyQ0gOJ8ZWqkY=
: cdrwa

$$ B实例
重新登录:
[zk: k8s:2181(CONNECTED) 2] ls /b
Authentication is not valid : /b

$$ A实例
[zk: localhost:2181(CONNECTED) 20] create /b/bb ''
Authentication is not valid : /b/bb
[zk: localhost:2181(CONNECTED) 21] addauth digest user:tpUq/4Pn5A64fVZyQ0gOJ8ZWqkY=
[zk: localhost:2181(CONNECTED) 22] create /b/bb ''                                 
Authentication is not valid : /b/bb

# 需要使用明文登录
[zk: localhost:2181(CONNECTED) 23] addauth digest user:password
[zk: localhost:2181(CONNECTED) 24] create /b/bb '' 
Created /b/bb 

# 权限没有继承性
[zk: localhost:2181(CONNECTED) 25] getAcl /b/bb
'world,'anyone
: cdrwa

IP

ip的权限配置更简单些。逻辑就是匹配客户端的IP地址,在权限IP地址段范围内的才能访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$$ A实例
[zk: localhost:2181(CONNECTED) 18] setAcl /i ip:127.0.0.1:cdrwa
...
[zk: localhost:2181(CONNECTED) 19] getAcl /i
'ip,'127.0.0.1
: cdrwa
[zk: localhost:2181(CONNECTED) 24] get /i
Authentication is not valid : /i

咋回事呢,就是本地还没权限?有时可localhost不一定对应127.0.0.1的。。。

$$ B实例
[root@k8s zookeeper-3.4.10]# bin/zkCli.sh -server 127.0.0.1
[zk: 127.0.0.1(CONNECTED) 0] get /i
i
...
改成另一个网卡的ip地址
[zk: 127.0.0.1(CONNECTED) 1] setAcl /i ip:192.168.191.138:cdrwa
...
[zk: 127.0.0.1(CONNECTED) 2] getAcl /i
'ip,'192.168.191.138
: cdrwa
[zk: 127.0.0.1(CONNECTED) 3] get /i
Authentication is not valid : /i

$$ C实例
用主机名(191.138)登录的实例
[zk: k8s(CONNECTED) 19] get /i
i

超级管理员

如果权限设置错了,咋办?

1
2
3
4
5
6
7
8
9
10
[zk: k8s(CONNECTED) 21] setAcl /i ip:192.168.191.0/24:cdrwa                   
Acl is not valid : /i

[zk: k8s(CONNECTED) 25] setAcl /i ip:192.168.191.0:cdrwa

[zk: k8s(CONNECTED) 26] getAcl /i
'ip,'192.168.191.0
: cdrwa
[zk: k8s(CONNECTED) 27] get /i
Authentication is not valid : /i

除非把客户端的ip地址换成 192.168.191.0 否则就访问不了了。

此时需要超级管理员才行,不然真没办法折腾了。(不知道为啥)是可以删掉(特指我当前的环境啊),但是这样数据就没有了啊!!

1
2
3
4
5
6
7
8
[zk: localhost:2181(CONNECTED) 26] getAcl /i
'ip,'192.168.191.0
: cdrwa
[zk: localhost:2181(CONNECTED) 27] delete /i
[zk: localhost:2181(CONNECTED) 28] ls /
[a, b, c, zookeeper, d, e]
[zk: localhost:2181(CONNECTED) 29] ls /i
Node does not exist: /i

如果数据很重要,重启后用超级管理员的方式找回密码还是很划的来的。

用 DigestAuthenticationProvider 加密就不操作了,直接用 es:es 对应的 es:es->es:KiHfMOSWCTgPKpz78IL/6qO8AEE= 作为管理员的账号密码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
export SERVER_JVMFLAGS=-Dzookeeper.DigestAuthenticationProvider.superDigest=es:KiHfMOSWCTgPKpz78IL/6qO8AEE=

[root@k8s zookeeper-3.4.10]# bin/zkServer.sh stop
[root@k8s zookeeper-3.4.10]# bin/zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.10/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

$$ A实例
[root@k8s zookeeper-3.4.10]# bin/zkCli.sh 
[zk: localhost:2181(CONNECTED) 0] get /i
Authentication is not valid : /i
[zk: localhost:2181(CONNECTED) 1] getAcl /i
'ip,'192.168.191.0
: cdrwa
[zk: localhost:2181(CONNECTED) 2] addauth digest es:es
[zk: localhost:2181(CONNECTED) 3] get /i
i
...
[zk: localhost:2181(CONNECTED) 4] setAcl /i world:anyone:cdrwa
...

$$ B实例
[zk: localhost:2181(CONNECTED) 0] get /i
i
[zk: localhost:2181(CONNECTED) 1] getAcl /i
'world,'anyone
: cdrwa

实践—好玩

权限可以直接在创建的时刻指定:

1
create /mynode content digest:user:tpUq/4Pn5A64fVZyQ0gOJ8ZWqkY=:cdrwa

也可以一次性设置N个权限:

注:以下操作都是超级管理员登录的窗口,所以不存在权限的问题。想怎么改就怎么改

1
2
3
4
5
6
7
8
9
setAcl /i ip:192.168.191.0:cdrwa,ip:127.0.0.1:cdrwa,ip:192.168.191.138:cdrwa

getAcl /i
'ip,'192.168.191.0
: cdrwa
'ip,'127.0.0.1
: cdrwa
'ip,'192.168.191.138
: cdrwa

但是,使用ip、digest、word重设权限后,会覆盖旧的:

1
2
3
4
5
6
7
8
9
[zk: localhost:2181(CONNECTED) 7] setAcl /i ip:0.0.0.0:cdrwa
[zk: localhost:2181(CONNECTED) 8] getAcl /i
'ip,'0.0.0.0
: cdrwa

[zk: localhost:2181(CONNECTED) 15] setAcl /i world:anyone:cdraw
[zk: localhost:2181(CONNECTED) 16] getAcl /i
'world,'anyone
: cdrwa

3.4的版本不支持ip段(3.5应该是ok的): IPAuthenticationProvider

1
2
3
public boolean isValid(String id) {
    return addr2Bytes(id) != null;
}

可以找对应版本的源码(远程)调试下:

1
2
[root@k8s zookeeper-3.4.10]# export SERVER_JVMFLAGS="-Dzookeeper.DigestAuthenticationProvider.superDigest=es:KiHfMOSWCTgPKpz78IL/6qO8AEE= -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
[root@k8s zookeeper-3.4.10]# bin/zkServer.sh start

auth的权限比较有意思:自家兄弟添加、排除异己;permission按最新的算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
[zk: localhost:2181(CONNECTED) 21] setAcl /i auth::cdrwa,ip:0.0.0.0:cd
...
[zk: localhost:2181(CONNECTED) 22] getAcl /i
'ip,'0.0.0.0
: cd
'digest,'es:KiHfMOSWCTgPKpz78IL/6qO8AEE=
: cdrwa

# auth add
[zk: localhost:2181(CONNECTED) 27] addauth digest m:m
[zk: localhost:2181(CONNECTED) 28] addauth digest n:n
[zk: localhost:2181(CONNECTED) 29] setAcl /i auth::cdrwa
...
[zk: localhost:2181(CONNECTED) 30] getAcl /i
'digest,'es:KiHfMOSWCTgPKpz78IL/6qO8AEE=
: cdrwa
'digest,'m:WZiIgWqJgd8EQVBh55Bslf/7JRc=
: cdrwa
'digest,'n:TZ3f1UF7B75EF5g6qWR0VmEvb/s=
: cdrwa

# perm
[zk: localhost:2181(CONNECTED) 31] addauth digest z:z
[zk: localhost:2181(CONNECTED) 32] addauth digest l:l
[zk: localhost:2181(CONNECTED) 33] setAcl /i auth:z:z:cd
...
[zk: localhost:2181(CONNECTED) 34] getAcl /i
'digest,'es:KiHfMOSWCTgPKpz78IL/6qO8AEE=
: cd
'digest,'m:WZiIgWqJgd8EQVBh55Bslf/7JRc=
: cd
'digest,'n:TZ3f1UF7B75EF5g6qWR0VmEvb/s=
: cd
'digest,'z:cOgtYxFOAwKiTCMigcN2j2fFI3c=
: cd
'digest,'l:gdlgatwJdq7uG8kFfIjcIZj0tnQ=
: cd

可以看到全部变成cd了

[zk: localhost:2181(CONNECTED) 35] setAcl /i auth:z:z:cdraw
...
[zk: localhost:2181(CONNECTED) 36] getAcl /i               
'digest,'es:KiHfMOSWCTgPKpz78IL/6qO8AEE=
: cdrwa
'digest,'m:WZiIgWqJgd8EQVBh55Bslf/7JRc=
: cdrwa
'digest,'n:TZ3f1UF7B75EF5g6qWR0VmEvb/s=
: cdrwa
'digest,'z:cOgtYxFOAwKiTCMigcN2j2fFI3c=
: cdrwa
'digest,'l:gdlgatwJdq7uG8kFfIjcIZj0tnQ=
: cdrwa

全部变成cdrwa

我觉得用 auth 设置权限是最保险的,不会搞错了出现自己都访问不了的情况。

后记

ok,到此基本的知识点算大概了解了。还有自定义实现授权的provider,这有点高级了有兴趣的自己去看官方文档了。

但是因为权限没有继承关系,像一些开源项目用到zookeeper的话,怎么进行加密呢?所有子目录都一个个的加?或者自定义根路径(chroot)让别人猜不到?

还有像zookeeper自己的目录 /zookeeper ,怎么进行权限管理呢?

–END

Comments