1.認識一下控制命令
要了解ZooKeeper的ACL權限控制,首先我們需要了解一下ZooKeeper客戶端的權限控制的命令:
1、創建zk節點時,可以同時指定權限信息(可選)
create [-s] [-e] [-c] [-t ttl] path [data] [acl]
2、根據路徑設置節點的權限(對一個已存在的節點)
setAcl [-s] [-v version] [-R] path acl
3、根據路徑獲取節點的權限
getAcl [-s] path
4、認證授權信息的命令(auth格式為用戶名:密碼, 密碼為明文密碼)
addauth scheme auth
那么 create 和 setAcl 中 acl 應該是什么樣的格式呢?
[zk: localhost:2181(CONNECTED) 0] create /test
Created /test
[zk: localhost:2181(CONNECTED) 1] setAcl /test acl
acl does not have the form scheme:id:perm
Acl is not valid : /test
- 首先,創建一個新節點
/test - 然后,用
setAcl為節點/test設置權限 - 接着,我們讀這段報錯
acl does not have the form scheme:id:perm
我們知道了 acl 應該由三部分組成:scheme, id, perm。
2. ACL 權限控制
Zookeeper 的 ACL 權限控制,可以控制節點的讀寫操作,保證數據的安全性,Zookeeper ACL 權限設置分為 3 部分組成,分別是:權限模式(Scheme)、授權對象(ID)、權限信息(Permission)。最終組成⼀條例如scheme:id:permission 格式的 ACL 請求信息。
ZooKeeper 可以給每個節點設置不同的權限控制。
2.1 權限 Permission
首先,我們可能關心,對於某個數據節點,到底有哪些細分的權限呢?
權限就是指我們可以在數據節點上執⾏的操作種類(CRUD),如下所示:在 ZooKeeper 中已經定義好的權限有 5 種:
- 數據節點(c: create)創建權限,授予權限的對象可以在數據節點下創建⼦節點;
- 數據節點(w: wirte)更新權限,授予權限的對象可以更新該數據節點;
- 數據節點(r: read)讀取權限,授予權限的對象可以讀取該節點的內容以及⼦節點的列表信息;
- 數據節點(d: delete)刪除權限,授予權限的對象可以刪除該數據節點的⼦節點;
- 數據節點(a: admin)管理者權限,授予權限的對象可以對該數據節點體進⾏ ACL 權限設置。
1 和 4 其實就對應zk命令
create和delete;
2 和 3 主要對應zk命令set和get;
5 主要對應zk命令setAcl和getAcl;
2.2 權限模式 Scheme
接着,我們就該了解,到底有哪些授權的模式?而授權對象(ID)其實是和權限模式(Scheme)對應使用的。
-
所有人可用模式(world:anyone:perm): world模式只有一個授權對象id,anyone,表示任何一個人都有權限。
-
僅當前認證用戶可用模式(auth:user:password:perm): auth模式的授權對象是當前認證用戶。
- 提供此方案是為了方便用戶創建znode,然后將對該znode的訪問限制為僅該用戶,這是一個常見的用例;
- 如果沒有通過身份驗證的用戶,則使用身份驗證方案設置ACL將失敗;
-
口令認證模式(digest:user:password:perm): digest模式使用 username:password 字符串生成MD5散列,然后將其用作授權對象ID。
- 這里的 password 不是明文,而是 base64編碼(SHA1摘要算法(明文密碼))的結果。因此和 addauth 的使用習慣不相同;
- 這里面還有一種特殊情況,就是認證 超級管理員 的口令后,ZooKeeper客戶端可以對 ZooKeeper 上的任意數據節點進⾏任意操作;
-
IP認證模式:ip(ip:addr:perm 或者 ip:addr/bits:perm)模式使用客戶端主機ip作為授權對象ID。
- 可以針對⼀個 IP 或者⼀段 IP 地址授予某種權限。
- ⽐如我們可以讓⼀個 IP 地址為“ip:192.168.0.110”的機器對服務器上的某個數據節點具有寫⼊的權限。
- 或者也可以通過“ip:192.168.0.1/24”給⼀段 IP 地址的機器賦權。
-
SSL安全認證模式:x509模式,使用安全端口時,客戶端將自動進行身份驗證,並設置x509方案的身份驗證信息。
- 如果對這種方式感興趣,見參考文檔 ZooKeeper安全認證機制:SSL 閱讀
3. 實戰
3.1 如何獲取加密的密碼?
我們已經知道了 digest認證模式需要用到 base64編碼的 SHA1密碼,那么怎么獲取呢?
創建一個Maven項目,加入依賴:
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.9</version>
</dependency>
然后,寫一個Main程序:
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
import java.security.NoSuchAlgorithmException;
public class Main {
public static void main(String[] args) throws NoSuchAlgorithmException {
// 輸出:reader:FIlPshmj74ilCpU6QOfBN00zY9w=
System.out.println(DigestAuthenticationProvider.generateDigest("reader:reader"));
// 輸出:reader:JLVf6B6eexF5jTpORnfdSP/IFVk=,但這個是錯誤的
// System.out.println(DigestAuthenticationProvider.generateDigest("reader"));
}
}
易錯點: DigestAuthenticationProvider.generateDigest 參數是 user:password 格式,而不是單純的 password。
- 雖然,直接使用 reader 作為參數不會報錯,但是你
addauth reader:reader path會一直驗證失敗。
3.2 setAcl授權和addauth認證
[zk: localhost:2181(CONNECTED) 0] create -e /test
Created /test
[zk: localhost:2181(CONNECTED) 1] setAcl /test digest:reader:FIlPshmj74ilCpU6QOfBN00zY9w=:r
[zk: localhost:2181(CONNECTED) 2] get /test
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /test
[zk: localhost:2181(CONNECTED) 3] addauth digest reader:reader
[zk: localhost:2181(CONNECTED) 4] get /test
null
[zk: localhost:2181(CONNECTED) 5] quit
- 首先,創建一個臨時數據結點
/test, - 然后,設置口令認證訪問該結點,
- 在 addauth 之前,是沒有權限讀取
/test的數據;但是在 addauth 之后,允許讀操作, - 最后,退出當前客戶端,臨時節點
/test會被清除。
然后,我們再看一下使用錯誤的密碼摘要的情況:

3.3 create授權和addauth認證
創建節點的同時,進行授權,這個地方有個易錯點:
[zk: localhost:2181(CONNECTED) 0] create -e /test digest:reader:FIlPshmj74ilCpU6QOfBN00zY9w=:r
Created /test
[zk: localhost:2181(CONNECTED) 1] get /test
digest:reader:FIlPshmj74ilCpU6QOfBN00zY9w=:r
[zk: localhost:2181(CONNECTED) 2] quit
你會發現,你沒有 addauth 也得到了數據,但是數據和你的acl一樣。
問題就在於,如果你想在創建節點的同時設置權限控制,那么你就必須初始化數據。否則,就會把acl字符串當成數據存儲。
所以,我們調整一下:
[zk: localhost:2181(CONNECTED) 0] create -e /test data digest:reader:FIlPshmj74ilCpU6QOfBN00zY9w=:r
Created /test
[zk: localhost:2181(CONNECTED) 1] get /test
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /test
[zk: localhost:2181(CONNECTED) 2] addauth digest reader:reader
[zk: localhost:2181(CONNECTED) 3] get /test
data
[zk: localhost:2181(CONNECTED) 3] quit
3.4 能否給同一個結點設置多個ACL?
首先,我又准備了幾個賬號:
| user:password | user:digest |
|---|---|
| user1:pwd1 | user1:a9l5yfb9zl8WCXjVmi5/XOC0Ep4= |
| user2:pwd2 | user2:LJcj8Pt1rGm2pXKbdJDGH8+Bn+0= |
| user3:pwd3 | user3:vTWpf7+XOMH/ifDkxE6KmhSUCpA= |
操作如下:
[zk: localhost:2181(CONNECTED) 0] create /user1 LiLei digest:user1:a9l5yfb9zl8WCXjVmi5/XOC0Ep4=:ra
Created /user1
[zk: localhost:2181(CONNECTED) 1] get /user1
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /user1
[zk: localhost:2181(CONNECTED) 2] addauth digest user1:pwd1
[zk: localhost:2181(CONNECTED) 3] get /user1
LiLei
[zk: localhost:2181(CONNECTED) 4] getAcl /user1
'digest,'user1:a9l5yfb9zl8WCXjVmi5/XOC0Ep4=
: ra
[zk: localhost:2181(CONNECTED) 5] setAcl /user1 digest:user2:LJcj8Pt1rGm2pXKbdJDGH8+Bn+0=:rw
[zk: localhost:2181(CONNECTED) 6] getAcl /user1
Authentication is not valid : /user1
[zk: localhost:2181(CONNECTED) 7] addauth digest user2:pwd2
[zk: localhost:2181(CONNECTED) 8] getAcl /user1
'digest,'user2:x
: rw
[zk: localhost:2181(CONNECTED) 9] quit
- 首先,創建數據結點
/user1,並且授權口令user1:pwd1允許讀取和管理權限; - 接着,使用
user1:pwd1完成 addauth 授權認證,並讀取到了/user1對應的數據和權限信息; - 然后,修改了 ACL,原先的
user1:pwd1口令失效了; - 重新使用
user2:pwd2完成 addauth 授權認證,再次讀取權限信息,此時發現新的權限信息覆蓋了原來的權限信息;
綜上所述,同一個znode不支持多個 ACL。
3.5 多次addauth可以獲取權限合集?
實驗如下:
[zk: localhost:2181(CONNECTED) 0] create /user2 Lisa digest:user2:LJcj8Pt1rGm2pXKbdJDGH8+Bn+0=:ra
Created /user2
[zk: localhost:2181(CONNECTED) 1] create /user3 Simon digest:user3:vTWpf7+XOMH/ifDkxE6KmhSUCpA=:ra
Created /user3
[zk: localhost:2181(CONNECTED) 2] addauth digest user2:pwd2
[zk: localhost:2181(CONNECTED) 3] get /user2
Lisa
[zk: localhost:2181(CONNECTED) 4] get /user3
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /user3
[zk: localhost:2181(CONNECTED) 5] addauth digest user3:pwd3
[zk: localhost:2181(CONNECTED) 7] get /user2
Lisa
[zk: localhost:2181(CONNECTED) 8] get /user3
Simon
[zk: localhost:2181(CONNECTED) 9] quit
由此可見,addauth 會取當前客戶端認證后獲取的權限合集。
3.6 權限受限怎么辦?
- 第一個方法是禁用 ACL 權限控制;
- 第二個方法是超級管理員模式。
本文主要說的是 Docker 容器運行的 ZooKeeper 如何處理?
3.6.1 禁用 ACL 權限控制
★ 可以通過配置文件zoo.cfg中添加skipACL=yes進⾏配置,默認是no,可以配置為true, 則配置過的 ACL 將不再進⾏權限檢測:
首先,用 docker images 查看下載的鏡像:

如圖所示,30993cacc7c9 就是 zookeeper:3.5.9 的 鏡像ID。
# 簡單解釋一下參數:
# --name是給啟動的容器取的名字,以后啟動容器可以使用這個名字來啟動
# -p 宿主主機端口:容器端口, 2181 是 zookeeper 的默認端口號, 因為宿主機已經有其他zookeeper容器占用了2181,所以此處改為2281
# --restart always 表示容器如果關閉退出就是重啟
# -d 表示容器以后台守護進程啟動
# -v 宿主機文件路徑:容器文件路徑
# 末尾的 30993cacc7c9 表示鏡像ID
docker run --name zookeeper-1 -p 2281:2181 --restart always -d -v D:\DockerContainer\zookeeper\zoo.cfg:/conf/zoo.cfg 30993cacc7c9
然后 D:\DockerContainer\zookeeper\zoo.cfg 文件內容為:
skipACL=yes
dataDir=/data
dataLogDir=/datalog
tickTime=2000
initLimit=5
syncLimit=2
autopurge.snapRetainCount=3
autopurge.purgeInterval=0
maxClientCnxns=60
standaloneEnabled=true
admin.enableServer=true
server.1=localhost:2888:3888;2181
接着,登入容器 docker exec -it zookeeper-1 bash,啟動zk客戶端:

然后,做操作都不需要權限了:
[zk: localhost:2181(CONNECTED) 0] create /test data digest:user1:a9l5yfb9zl8WCXjVmi5/XOC0Ep4=:w
Created /test
[zk: localhost:2181(CONNECTED) 1] get /test
data
[zk: localhost:2181(CONNECTED) 2] delete /test
★ 可以通過設置額外配置參數skipACL=yes進⾏配置,默認是no,可以配置為true, 則配置過的ACL將不再進⾏權限檢測
docker run --name=zookeeper-2 -p 2381:2181 --restart always -d -e ZOO_CFG_EXTRA="skipACL=yes" 30993cacc7c9
★ 可以通過添加JVM參數-Dzookeeper.skipACL=yes進⾏配置,默認是no,可以配置為true, 則配置過的ACL將不再進⾏權限檢測
docker run --name=zookeeper-3 -p 2481:2181 --restart always -d -e JVMFLAGS="-Dzookeeper.skipACL=yes" 30993cacc7c9
跳過權限驗證,就意味着所有客戶端都可以不用認證操作所有的數據節點。
3.6.2 超級管理員super
設置zk啟動時的JVM參數為 -Dzookeeper.DigestAuthenticationProvider.superDigest=super1:WqkSAJNIl+iMSE0y/0xAI3lPT5o= 指定超管賬號口令為 super1:admin。
docker run --name=zookeeper-4 -p 2581:2181 --restart always -d -e JVMFLAGS="-Dzookeeper.DigestAuthenticationProvider.superDigest=super1:WqkSAJNIl+iMSE0y/0xAI3lPT5o=" 30993cacc7c9
接着,登入容器並啟動zk客戶端:

以下實驗部分:
[zk: localhost:2181(CONNECTED) 0] create /test data digest:user1:a9l5yfb9zl8WCXjVmi5/XOC0Ep4=:w
Created /test
[zk: localhost:2181(CONNECTED) 1] get /test
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /test
[zk: localhost:2181(CONNECTED) 2] addauth digest super1:admin
[zk: localhost:2181(CONNECTED) 3] get /test
data
[zk: localhost:2181(CONNECTED) 4] quit
- 我們允許用戶 user1 對路徑為
/test的節點進行數據的修改; - 我們獲取數據時,提示沒有權限;
- 此時,賦予當前客戶端超級管理員權限;
- 超級管理員的所有操作都會跳過權限檢查。
簡單的客戶端代碼:
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
public class Test {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
Watcher connectedWatcher = event -> {
if (Watcher.Event.KeeperState.SyncConnected.equals(event.getState())
&& Watcher.Event.EventType.None.equals(event.getType())
&& event.getPath() == null) {
System.out.println("========= ZooKeeper connected ==========");
latch.countDown();
}
};
try (ZooKeeper zk = new ZooKeeper("localhost:2581", 30, connectedWatcher)) {
latch.await();
try {
byte[] data = zk.getData("/test", null, null);
Optional.ofNullable(data).map(String::new).ifPresent(value -> System.out.println("Node /test data: " + value));
} catch (KeeperException e) {
System.out.println("Something wrong:" + e.getMessage());
}
zk.addAuthInfo("digest", "super1:admin".getBytes());
System.out.println("========= after addauth super ==========");
try {
byte[] data = zk.getData("/test", null, null);
Optional.ofNullable(data).map(String::new).ifPresent(value -> System.out.println("Node /test data: " + value));
} catch (KeeperException e) {
System.out.println("Something wrong:" + e.getMessage());
}
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
執行結果:
========= ZooKeeper connected ==========
Something wrong:KeeperErrorCode = NoAuth for /test
========= after addauth super ==========
Node /test data: data
參考文檔
ZooKeeper ACLPermissions 閱讀
ZooKeeper安全認證機制:SSL 閱讀
Docker ZooKeeper 官方文檔 閱讀
docker windows下掛載目錄和文件 閱讀
Zookeeper Acl權限 超級用戶權限 怎么跳過ACL密碼/賬戶驗證 閱讀
