zk原始碼閱讀9:資料模型ACL之基礎資料結構以及驗證
摘要
DataTree涉及到ACL,本節先講解ACL相關內容
講ACL的參考資料並不是很多,書上也沒有講原理實現,這裡自己整理一下
本文主要講解
ACL簡介
ACL資料結構
perms
Id(id,schema)
內建許可權,ACL列表,Id,schema
ACL的建立,修改
ACL的驗證
ACL建立修改的驗證(create,setACL)
ACL申請許可權的驗證(各種操作)
簡介
ZooKeeper使用ACL來控制訪問其znode(ZooKeeper的資料樹的資料節點)。ACL的實現方式非常類似於UNIX檔案的訪問許可權:它採用訪問許可權位 允許/禁止 對節點的各種操作以及能進行操作的範圍。不同於UNIX許可權的是,ZooKeeper的節點不侷限於 使用者(檔案的擁有者),組和其他人(其它)這三個標準範圍。ZooKeeper不具有znode的擁有者的概念。相反,ACL指定id集以及與之對應的許可權。
還要注意的是一條ACL僅針對於一個特定的節點。尤其不適用於子節點。
例如,如果/app 只對IP:172.16.16.1可讀 而 / APP/status 是對任何人可讀的,ACL不是遞迴的。
ZooKeeper支援可插拔的身份驗證方案。 id使用如下形式 scheme:id,其中 scheme 是 id 所對應一個認證方案。例如,IP:172.16.16.1,id為主機的地址 172.16.16.1。
當客戶端連線到ZooKeeper驗證自己時,ZooKeeper將有關該客戶端的所有Id與客戶連線關聯。客戶端試圖訪問一個節點時,這些ID與該znodes的ACL驗證。 ACL是由(scheme:expression, perms)對構成。其中expression的格式指定為scheme。例如,(IP:19.22.0.0/16,READ)表示對所有起始IP為19.22的客戶端具有讀許可權。
一個ZooKeeper的節點(znode)儲存兩部分內容:資料和狀態,狀態中包含ACL資訊。建立一個znode會產生一個ACL列表。
那麼,ACL具體是什麼呢,怎麼實現的?
ACL資料結構
程式碼裡面涉及ACL機制的類有
org.apache.zookeeper.data.ACL
包含許可權perms與Id(見下)
org.apache.zookeeper.data.Id
包含驗證模式schema和提供的驗證內容id
org.apache.zookeeper.ZooDefs
提供內建的OpCode
許可權Perms
ACL列表定義Ids
先用一張圖說明ACL與Id這兩個類的依賴關係
也可以說,每個ACL包括:
驗證模式(scheme)
具體內容(Id)(當scheme=“digest”時,Id為使用者名稱密碼,例如“root:J0sTy9BCUKubtK1y8pkbL7qoxSw=”)
許可權(perms)
下面分開進行介紹這兩個結構(perms,Id),也可以說是三個結構(perms,id,schema),這裡根據類的定義來,還是當成兩個資料結構來講,ACL資料結構如下
public class ACL implements Record {
private int perms;
private Id id;
}
許可權perms
目前,節點的許可權(perms)有以下幾種,在org.apache.zookeeper.ZooDefs.Perms 中定義
int READ = 1 << 0;//允許對本節點GetChildren和GetData操作
int WRITE = 1 << 1;//允許對本節點SetData操作
int CREATE = 1 << 2;//允許對子節點Create操作
int DELETE = 1 << 3;//允許對子節點Delete操作
int ADMIN = 1 << 4;//允許對本節點setAcl操作
int ALL = READ | WRITE | CREATE | DELETE | ADMIN;//這個是組合許可權
ACL許可權用一個int型數字perms表示
perms的5個二進位制位分別表示setacl、delete、create、write、read
比如0x1f=adcwr,0x1=----r,0x15=a-c-r。
除了ALL以外,其他都是最細粒度的許可權,可以用|,&來自己定義perms的組合許可權
Id
包含驗證模式schema以及提供驗證的內容id
目前zk提供了兩個內建的Id,在org.apache.zookeeper.ZooDefs.Ids中定義
/**
* This Id represents anyone.
*/
public final Id ANYONE_ID_UNSAFE = new Id("world", "anyone");//固定使用者為anyone,為所有Client端開放許可權
/**
* This Id is only usable to set ACLs. It will get substituted with the
* Id's the client authenticated with.
*/
public final Id AUTH_IDS = new Id("auth", "");//不使用任何id,代表任何已確認使用者。
以及在org.apache.zookeeper.server.auth.DigestAuthenticationProvider#handleAuthentication 定義的
new Id("super", "")//在這種scheme情況下,對應的id擁有超級許可權,可以做任何事情(cdrwa)
除了內建Id以外,還有內建的schema提供認證模式,但是沒有對應的預設id(因為都是動態提供的)
schema
除了上面內建Id定義的world和auth,super這三個schema,還有無固定id的內建的schema即驗證模式
驗證模式以及驗證方法通過AuthenticationProvider實現
最終在ProviderRegistry中進行註冊
digest:Client端由使用者名稱和密碼驗證,譬如user:password,digest的密碼生成方式是Sha1摘要的base64形式
ip:Client端由IP地址驗證,譬如172.2.0.0/24
Sasl: 這個類定義了,但是並沒有註冊,我也並不清楚這個認證方式
下面對Id做一個總結
schema | id | 意義 | 備註 |
---|---|---|---|
auth | "" | 不使用任何id,代表任何已確認使用者 | 自帶Id |
world | anyone | 固定使用者,為所有Client端開放許可權 | 自帶Id |
super | "" | 擁有超級許可權,可以做任何事情(cdrwa) | 自帶Id |
ip | 無固定值,有固定格式(ip expression) | IP驗證方式 | 自帶schema |
digest | 無固定值,有固定格式(digest expression) | 使用者名稱和密碼驗證,再生成摘要 | 自帶schema |
sasl | 無固定值,有固定格式(sasl expression) | sasl驗證方式,這個我並不是很懂 | 自帶schema |
ACL的建立與修改
只有兩類API會改變Znode的ACL列表:一個是create(),一個是setACL()。
這兩個方法都要求傳入一個List。Server接到這兩種更新請求後,會判斷指定的每一個ACL中,scheme對應的AuthenticationProvider是否存在。
如果存在,呼叫其isValid(String)方法判斷對應的id表示式是否合法
具體參見PrepRequestProcessor.fixupACL()方法。
ACL的驗證
ACL建立修改時的驗證
只在create和setACL操作中涉及ACL的建立與修改,具體參見PrepRequestProcessor.fixupACL()
private boolean fixupACL(List<Id> authInfo, List<ACL> acl) {
if (skipACL) {
return true;
}
if (acl == null || acl.size() == 0) {
return false;
}
Iterator<ACL> it = acl.iterator();
LinkedList<ACL> toAdd = null;
while (it.hasNext()) {
ACL a = it.next();
Id id = a.getId();
if (id.getScheme().equals("world") && id.getId().equals("anyone")) {//如果是固定使用者,為所有Client端開放許可權
// wide open
} else if (id.getScheme().equals("auth")) {
// This is the "auth" id, so we have to expand it to the
// authenticated ids of the requestor
it.remove();//如果是auth,把這個從acl的List中刪掉
if (toAdd == null) {
toAdd = new LinkedList<ACL>();
}
boolean authIdValid = false;
for (Id cid : authInfo) {
/*
一般情況下,預設的Id只有IP這一種(org.apache.zookeeper.server.NIOServerCnxn.NIOServerCnxn),裡面呼叫了
authInfo.add(new Id("ip", addr.getHostAddress()));
*/
AuthenticationProvider ap =
ProviderRegistry.getProvider(cid.getScheme());
if (ap == null) {
LOG.error("Missing AuthenticationProvider for "
+ cid.getScheme());
} else if (ap.isAuthenticated()) {//如果驗證過了,三種實現中,IP返回false,其他兩種返回true
authIdValid = true;
toAdd.add(new ACL(a.getPerms(), cid));
}
}
if (!authIdValid) {
return false;
}
} else {//其他認證模式的話,如ip,digest,sasl
AuthenticationProvider ap = ProviderRegistry.getProvider(id
.getScheme());
if (ap == null) {
return false;
}
if (!ap.isValid(id.getId())) {//如果id的格式不valid
return false;
}
}
}
if (toAdd != null) {
for (ACL a : toAdd) {
acl.add(a);
}
}
return acl.size() > 0;//確保有一種方式認證通過了
}
簡而言之,這個函式就是看設定的ACL值是否合理,基本過程如下
1.如果acl列表有("world","anyone"),那麼一定認證通過
2.上述情況外,如果是Id的schema是"auth",那麼要看請求攜帶的authInfo是否是isAuthenticated的,是的話認證通過
3.上述情況外,一般就是“ip”,"digest","sasl",呼叫對應認證提供器的isValid方法校驗id內容格式是否valid,是的話認證通過
例項分析
下面分析一個用"auth",""這個Id建立節點出現的異常
背景
String path1 = zk.create("/test21", "asd".getBytes(),
ZooDefs.Ids.CREATOR_ALL_ACL,
CreateMode.EPHEMERAL);
其中CREATOR_ALL_ACL定義在org.apache.zookeeper.ZooDefs.Ids#CREATOR_ALL_ACL中
public final ArrayList<ACL> CREATOR_ALL_ACL = new ArrayList<ACL>(
Collections.singletonList(new ACL(Perms.ALL, AUTH_IDS)));//用到了"auth",""
出現了異常
org.apache.zookeeper.KeeperException$InvalidACLException: KeeperErrorCode = InvalidACL for /test21
原理分析
NIOServerCnxn#NIOServerCnxn只給request的authInfo加了"ip"這個schema
PrepRequestProcessor#fixupACL中,處理邏輯如下
也就是說("auth","")應該和sasl或者digest這種schema配合起來使用才行,這裡就深究如何使用"auth",""了
申請許可權時的驗證(以create為例)
應該會在後面處理鏈的時候講,這裡帶過一下
比如在parentNode中進行createNode操作,參見
org.apache.zookeeper.server.PrepRequestProcessor#pRequest2Txn 中 case OpCode.create
呼叫了
checkACL(zks, parentRecord.acl, ZooDefs.Perms.CREATE,
request.authInfo);//驗證是否有create許可權
checkACL函式如下
/**
*
* @param zks
* @param acl 對應節點或者父節點擁有的許可權
* @param perm 目前操作需要的許可權
* @param ids 目前請求提供的許可權
* @throws KeeperException.NoAuthException
*/
static void checkACL(ZooKeeperServer zks, List<ACL> acl, int perm,
List<Id> ids) throws KeeperException.NoAuthException {
if (skipACL) {//如果跳過ACL
return;
}
if (acl == null || acl.size() == 0) {//如果沒有要求的ACL
return;
}
for (Id authId : ids) {
if (authId.getScheme().equals("super")) {//如果提供的ACL有超級許可權
return;
}
}
for (ACL a : acl) {
Id id = a.getId();
if ((a.getPerms() & perm) != 0) {//如果對應的節點擁有perm許可權
if (id.getScheme().equals("world")
&& id.getId().equals("anyone")) {
return;//如果請求提供了超級許可權
}
AuthenticationProvider ap = ProviderRegistry.getProvider(id
.getScheme());//根據策略模式獲取對應的認證提供器
if (ap != null) {
for (Id authId : ids) {//用認證器一個個 認證 請求提供的Id
if (authId.getScheme().equals(id.getScheme())
&& ap.matches(authId.getId(), id.getId())) {//模式相同並且匹配通過
//這要有一個匹配通過就行
return;
}
}
}
}
//如果對應的節點都沒有要求的perm許可權,那就驗證失敗,和請求提供什麼許可權無關
}
throw new KeeperException.NoAuthException();
}
簡而言之,就是當前節點acl包含當前操作許可權perm,並且當前節點acl能夠認證通過請求提供的ids許可權(有一個認證通過就行)
思考
注意上面描述的Id與id的區別
在Id這個類中,有id這個屬性,注意大小寫
ACL與Id的關係
ACL包含perms與Id
Id包含Id和schema
("super","")和("world","anyone")許可權比較
從上面的checkACL函式來講,先遇到("super","")就return
實際上,("super","")並沒有分配許可權,就像是程式開的後門,遇到了這個Id,就通行。
而("world","anyone"),還進行了perm的分配,有對應的ACL,參照ZooDefs.Ids#ANYONE_ID_UNSAFE
在上面checkACL函式中,該Id還受限於
if ((a.getPerms() & perm) != 0)
也就是說,原來的節點許可權不包含當前需要的perm許可權時,("world","anyone")也沒用
所以結論就是*** ("super","")許可權更大***
許可權的建立修改,以及申請許可權的驗證
在對節點進行create和setACL時涉及許可權的建立和修改,主要驗證acl列表的合理性
在org.apache.zookeeper.server.PrepRequestProcessor#fixupACL判斷
在對節點進行操作時,需要驗證當前請求以及相關節點是否有對應的許可權
在org.apache.zookeeper.server.PrepRequestProcessor#checkACL判斷
問題
建立最後將ACL資訊儲存在znode狀態中,這是怎麼實現的?
這個在後面請求鏈中再看
(auth,"")這個Id到底該怎麼配合digest或者sasl使用,沒有深究
吐槽
Id對應常量的地方有點亂,比如super定義在DigestAuthenticationProvider中
AuthenticationProvider 三個實現類的#isValid都沒有註釋
要自己看才知道對應schema該寫的id(即驗證內容)的格式應該是怎麼樣的
(auth,"")這個Id,使用說明太少了,沒看到demo也沒見到合理的資料
refer
主要參照 http://www.cnblogs.com/linuxbug/p/5023677.html
認證提供與註冊的相關介紹
一些定義
http://aliapp.blog.51cto.com/8192229/1327674
《從paxos到zookeeper》第7章
相關文章
- Redis原始碼學習——基礎資料結構之SDSRedis原始碼資料結構
- redis資料結構原始碼閱讀——字串編碼過程Redis資料結構原始碼字串編碼
- Redis基礎資料結構之字串Redis資料結構字串
- Redis基礎資料結構之MapRedis資料結構
- Redis基礎資料結構之SkipListRedis資料結構
- 基礎資料結構之陣列資料結構陣列
- 基礎資料結構之遞迴資料結構遞迴
- Redis基礎資料結構之連結串列Redis資料結構
- Magic原始碼閱讀(三)——資料匯入和構建原始碼
- Redis基礎資料結構Redis資料結構
- 資料結構基礎學習之緒論資料結構
- Coursera北大《資料結構基礎》之概論資料結構
- 實戰PHP資料結構基礎之棧PHP資料結構
- Python基礎之os和資料結構Python資料結構
- 資料結構基礎 連結串列資料結構
- 實戰PHP資料結構基礎之雙連結串列PHP資料結構
- 資料結構基礎知識資料結構
- 基礎資料結構大賞資料結構
- 實戰PHP資料結構基礎之遞迴PHP資料結構遞迴
- 資料結構基礎學習之線性表資料結構
- Python基礎之:Python的資料結構Python資料結構
- 實戰PHP資料結構基礎之佇列PHP資料結構佇列
- 實戰 PHP 資料結構基礎之遞迴PHP資料結構遞迴
- .NET基礎之資料繫結
- Oracle基礎結構之OS驗證和口令檔案驗證Oracle
- Redis基礎(一)資料結構與資料型別Redis資料結構資料型別
- 實戰PHP資料結構基礎之單連結串列PHP資料結構
- 資料結構 & 演算法 in Swift (一):Swift基礎和資料結構資料結構演算法Swift
- 搞懂:MVVM模型以及VUE中的資料繫結資料劫持釋出訂閱模式MVVM模型Vue模式
- Redis基礎——剖析基礎資料結構及其用法Redis資料結構
- Redis 資料結構之字串的那些騷操作 -- 像讀小說一樣讀原始碼Redis資料結構字串原始碼
- 大資料基礎架構總結大資料架構
- 資料結構基礎--雜湊表資料結構
- 淺析Redis基礎資料結構Redis資料結構
- 資料結構基礎第4講資料結構
- 資料結構基礎第3講資料結構
- PostgreSQL 原始碼解讀(10)- 插入資料#9(ProcessQuery)SQL原始碼
- 資料結構基礎學習之(棧和佇列)資料結構佇列