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与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实现

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章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容

  • 一、ZooKeeper的背景 1.1 认识ZooKeeper ZooKeeper---译名为“动物园管理员”。动物...
    algernoon阅读 8,980评论 1 106
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,048评论 18 139
  • ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,它包含一个简单的原语集,分布式应用程序可以基于...
    Prize阅读 224评论 0 1
  • 你, 不再是我女朋友, 我, 也不再是你男朋友, 爱, 却扯着我的心, 恨, 却在爱中找到一丝; 那傻傻的等待, ...
    林上的雪阅读 261评论 0 1
  • “世之人以为养形足以存生,而养形果不足以存生,则世奚足为哉!”世人都有一种迷信的思想,认为“养形足以存生”。找到好...
    庶矣斋阅读 601评论 0 6