摘要
DataTree涉及到ACL浪册,本節(jié)先講解ACL相關內容
講ACL的參考資料并不是很多,書上也沒有講原理實現(xiàn)岗照,這里自己整理一下
本文主要講解
ACL簡介
ACL數(shù)據(jù)結構
perms
Id(id,schema)
內置權限村象,ACL列表,Id攒至,schema
ACL的創(chuàng)建厚者,修改
ACL的驗證
ACL創(chuàng)建修改的驗證(create,setACL)
ACL申請權限的驗證(各種操作)
簡介
ZooKeeper使用ACL來控制訪問其znode(ZooKeeper的數(shù)據(jù)樹的數(shù)據(jù)節(jié)點)。ACL的實現(xiàn)方式非常類似于UNIX文件的訪問權限:它采用訪問權限位 允許/禁止 對節(jié)點的各種操作以及能進行操作的范圍迫吐。不同于UNIX權限的是库菲,ZooKeeper的節(jié)點不局限于 用戶(文件的擁有者),組和其他人(其它)這三個標準范圍志膀。ZooKeeper不具有znode的擁有者的概念熙宇。相反,ACL指定id集以及與之對應的權限溉浙。
還要注意的是一條ACL僅針對于一個特定的節(jié)點烫止。尤其不適用于子節(jié)點。
例如戳稽,如果/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與客戶連接關聯(lián)诞吱。客戶端試圖訪問一個節(jié)點時竭缝,這些ID與該znodes的ACL驗證房维。 ACL是由(scheme:expression, perms)對構成。其中expression的格式指定為scheme抬纸。例如咙俩,(IP:19.22.0.0/16,READ)表示對所有起始IP為19.22的客戶端具有讀權限。
一個ZooKeeper的節(jié)點(znode)存儲兩部分內容:數(shù)據(jù)和狀態(tài)阿趁,狀態(tài)中包含ACL信息膜蛔。創(chuàng)建一個znode會產生一個ACL列表。
那么脖阵,ACL具體是什么呢皂股,怎么實現(xiàn)的?
ACL數(shù)據(jù)結構
代碼里面涉及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),這里根據(jù)類的定義來呜呐,還是當成兩個數(shù)據(jù)結構來講,ACL數(shù)據(jù)結構如下
public class ACL implements Record {
private int perms;
private Id id;
}
權限perms
目前,節(jié)點的權限(perms)有以下幾種悍募,在org.apache.zookeeper.ZooDefs.Perms 中定義
int READ = 1 << 0;//允許對本節(jié)點GetChildren和GetData操作
int WRITE = 1 << 1;//允許對本節(jié)點SetData操作
int CREATE = 1 << 2;//允許對子節(jié)點Create操作
int DELETE = 1 << 3;//允許對子節(jié)點Delete操作
int ADMIN = 1 << 4;//允許對本節(jié)點setAcl操作
int ALL = READ | WRITE | CREATE | DELETE | ADMIN;//這個是組合權限
ACL權限用一個int型數(shù)字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(因為都是動態(tài)提供的)
schema
除了上面內置Id定義的world和auth,super這三個schema牢撼,還有無固定id的內置的schema即驗證模式
驗證模式以及驗證方法通過AuthenticationProvider實現(xiàn)
最終在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的創(chuàng)建與修改
只有兩類API會改變Znode的ACL列表:一個是create()灾杰,一個是setACL()。
這兩個方法都要求傳入一個List夭织。Server接到這兩種更新請求后吭露,會判斷指定的每一個ACL中,scheme對應的AuthenticationProvider是否存在尊惰。
如果存在讲竿,調用其isValid(String)方法判斷對應的id表達式是否合法
具體參見PrepRequestProcessor.fixupACL()方法。
ACL的驗證
ACL創(chuàng)建修改時的驗證
只在create和setACL操作中涉及ACL的創(chuàng)建與修改弄屡,具體參見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()) {//如果驗證過了,三種實現(xiàn)中,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;//確保有一種方式認證通過了
}
簡而言之壶笼,這個函數(shù)就是看設置的ACL值是否合理,基本過程如下
1.如果acl列表有("world","anyone"),那么一定認證通過
2.上述情況外神僵,如果是Id的schema是"auth",那么要看請求攜帶的authInfo是否是isAuthenticated的,是的話認證通過
3.上述情況外,一般就是“ip”,"digest","sasl"覆劈,調用對應認證提供器的isValid方法校驗id內容格式是否valid,是的話認證通過
實例分析
下面分析一個用"auth",""這個Id創(chuàng)建節(jié)點出現(xiàn)的異常
背景
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",""
出現(xiàn)了異常
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函數(shù)如下
/**
*
* @param zks
* @param acl 對應節(jié)點或者父節(jié)點擁有的權限
* @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) {//如果對應的節(jié)點擁有perm權限
if (id.getScheme().equals("world")
&& id.getId().equals("anyone")) {
return;//如果請求提供了超級權限
}
AuthenticationProvider ap = ProviderRegistry.getProvider(id
.getScheme());//根據(jù)策略模式獲取對應的認證提供器
if (ap != null) {
for (Id authId : ids) {//用認證器一個個 認證 請求提供的Id
if (authId.getScheme().equals(id.getScheme())
&& ap.matches(authId.getId(), id.getId())) {//模式相同并且匹配通過
//這要有一個匹配通過就行
return;
}
}
}
}
//如果對應的節(jié)點都沒有要求的perm權限,那就驗證失敗坤候,和請求提供什么權限無關
}
throw new KeeperException.NoAuthException();
}
簡而言之胁赢,就是當前節(jié)點acl包含當前操作權限perm,并且當前節(jié)點acl能夠認證通過請求提供的ids權限(有一個認證通過就行)
思考
注意上面描述的Id與id的區(qū)別
在Id這個類中,有id這個屬性白筹,注意大小寫
ACL與Id的關系
ACL包含perms與Id
Id包含Id和schema
("super","")和("world","anyone")權限比較
從上面的checkACL函數(shù)來講智末,先遇到("super","")就return
實際上,("super","")并沒有分配權限遍蟋,就像是程序開的后門吹害,遇到了這個Id,就通行虚青。
而("world","anyone")它呀,還進行了perm的分配,有對應的ACL,參照ZooDefs.Ids#ANYONE_ID_UNSAFE
在上面checkACL函數(shù)中,該Id還受限于
if ((a.getPerms() & perm) != 0)
也就是說纵穿,原來的節(jié)點權限不包含當前需要的perm權限時下隧,("world","anyone")也沒用
所以結論就是*** ("super","")權限更大***
權限的創(chuàng)建修改,以及申請權限的驗證
在對節(jié)點進行create和setACL時涉及權限的創(chuàng)建和修改谓媒,主要驗證acl列表的合理性
在org.apache.zookeeper.server.PrepRequestProcessor#fixupACL判斷
在對節(jié)點進行操作時淆院,需要驗證當前請求以及相關節(jié)點是否有對應的權限
在org.apache.zookeeper.server.PrepRequestProcessor#checkACL判斷
問題
創(chuàng)建最后將ACL信息保存在znode狀態(tài)中,這是怎么實現(xiàn)的?
這個在后面請求鏈中再看
(auth,"")這個Id到底該怎么配合digest或者sasl使用句惯,沒有深究
吐槽
Id對應常量的地方有點亂土辩,比如super定義在DigestAuthenticationProvider中
AuthenticationProvider 三個實現(xiàn)類的#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章