1. 程式人生 > 其它 >376.擺動序列

376.擺動序列

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

setAclacl 應該是什麼樣的格式呢?

[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
  1. 首先,建立一個新節點/test
  2. 然後,用setAcl為節點/test設定許可權
  3. 接著,我們讀這段報錯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 種:

  1. 資料節點(c: create)建立許可權,授予許可權的物件可以在資料節點下建立⼦節點;
  2. 資料節點(w: wirte)更新許可權,授予許可權的物件可以更新該資料節點;
  3. 資料節點(r: read)讀取許可權,授予許可權的物件可以讀取該節點的內容以及⼦節點的列表資訊;
  4. 資料節點(d: delete)刪除許可權,授予許可權的物件可以刪除該資料節點的⼦節點;
  5. 資料節點(a: admin)管理者許可權,授予許可權的物件可以對該資料節點體進⾏ ACL 許可權設定。

1 和 4 其實就對應zk命令 createdelete;
2 和 3 主要對應zk命令 setget;
5 主要對應zk命令 setAclgetAcl;

2.2 許可權模式 Scheme

接著,我們就該瞭解,到底有哪些授權的模式?而授權物件(ID)其實是和許可權模式(Scheme)對應使用的。

  1. 所有人可用模式(world:anyone:perm): world模式只有一個授權物件id,anyone,表示任何一個人都有許可權。

  2. 僅當前認證使用者可用模式(auth:user:password:perm): auth模式的授權物件是當前認證使用者。

    • 提供此方案是為了方便使用者建立znode,然後將對該znode的訪問限制為僅該使用者,這是一個常見的用例;
    • 如果沒有通過身份驗證的使用者,則使用身份驗證方案設定ACL將失敗;
  3. 口令認證模式(digest:user:password:perm): digest模式使用 username:password 字串生成MD5雜湊,然後將其用作授權物件ID。

    • 這裡的 password 不是明文,而是 base64編碼(SHA1摘要演算法(明文密碼))的結果。因此和 addauth 的使用習慣不相同;
    • 這裡面還有一種特殊情況,就是認證 超級管理員 的口令後,ZooKeeper客戶端可以對 ZooKeeper 上的任意資料節點進⾏任意操作;
  4. 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 地址的機器賦權。
  5. 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密碼/賬戶驗證 閱讀