深入理解 ZooKeeper的ACL实现(二)

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: 深入理解 ZooKeeper的ACL实现(二)

setACL源码追踪入口#


同样会和addAuth操作一样,主线程从控制台解析出用户的请求封装进request然后封装进pakcet发送给服务端


setACL服务端的处理逻辑#


请求来到服务端,在遇到第一次checkAcl之间,请求会顺利的来到第一个处理器PrepRequestProcessor, 所以我们的入口点就是这里


protected void pRequest(Request request) throws RequestProcessorException {
        // LOG.info("Prep>>> cxid = " + request.cxid + " type = " +
        // request.type + " id = 0x" + Long.toHexString(request.sessionId));
        request.hdr = null;
        request.txn = null;
    // todo 下面的不同类型的信息, 对应这不同的处理器方式
    try {
        switch (request.type) {
            case OpCode.create:
                // todo 创建每条记录对应的bean , 现在还是空的, 在面的pRequest2Txn 完成赋值
            CreateRequest createRequest = new CreateRequest();
            // todo 跟进这个方法, 再从这个方法出来,往下运行,可以看到调用了下一个处理器
            pRequest2Txn(request.type, zks.getNextZxid(), request, createRequest, true);
            break;
        case OpCode.delete:
            DeleteRequest deleteRequest = new DeleteRequest();               
            pRequest2Txn(request.type, zks.getNextZxid(), request, deleteRequest, true);
            break;
        case OpCode.setData:
            SetDataRequest setDataRequest = new SetDataRequest();                
            pRequest2Txn(request.type, zks.getNextZxid(), request, setDataRequest, true);
            break;
        case OpCode.setACL:
            // todo 客户端发送的setAcl命令, 会流经这个选项
            SetACLRequest setAclRequest = new SetACLRequest();
            /**  SetACLRequest的属性
             *   private String path;
             *   private java.util.List<org.apache.zookeeper.data.ACL> acl;
             *   private int version;
             */
            // todo 继续跟进去
            pRequest2Txn(request.type, zks.getNextZxid(), request, setAclRequest, true);
            break;
        case OpCode.check:


用户在控制台输入类似 setAcl /node4 digest:zhangsan:jA/7JI9gsuLp0ZQn5J5dcnDQkHA= 请求将被解析运行到上面的case OpCode.setACL:它new了一个空的对象SetACLRequest,这个对象一会在pRequest2Txn()函数中进行初始化


继续跟进pRequest2Txn(request.type, zks.getNextZxid(), request, setAclRequest, true);

源码如下: 它的解析我写在这段代码的下面


protected void pRequest2Txn(int type, long zxid, Request request, Record record, boolean deserialize)
        throws KeeperException, IOException, RequestProcessorException
    {
        // todo 使用request的相关属性,创建出 事务Header
        request.hdr = new TxnHeader(request.sessionId, request.cxid, zxid,
                                    Time.currentWallTime(), type);
        switch (type) {
            case OpCode.create:
                // todo 校验session的情况
                zks.sessionTracker.checkSession(request.sessionId, request.getOwner());
                CreateRequest createRequest = (CreateRequest)record;  
                .
                .
                .
            case OpCode.setACL:
                // todo 检查session的合法性
                zks.sessionTracker.checkSession(request.sessionId, request.getOwner());
                // todo record; 上一步中new 出来的SetACLRequest空对象,
                // todo 这样设计的好处就是, 可以进行横向的扩展, 让当前这个方法 PRequest2Tm()中可以被Record的不同实现类复用
                SetACLRequest setAclRequest = (SetACLRequest)record;
               // todo 将结果反序列化进 setAclRequest
                if(deserialize)
                    ByteBufferInputStream.byteBuffer2Record(request.request, setAclRequest);
                // todo 获取path 并校验
                path = setAclRequest.getPath();
                validatePath(path, request.sessionId);
                // todo 去除重复的acl
                listACL = removeDuplicates(setAclRequest.getAcl());
                if (!fixupACL(request.authInfo, listACL)) {
                    // todo request.authInfo的默认值就是本地ip, 如果没有这个值的话,在server本地,client都连接不上
                    throw new KeeperException.InvalidACLException(path);
                }
                //todo  获取当前节点的record
                nodeRecord = getRecordForPath(path);
                // todo 共用的checkACL 方法
                // todo  在setAcl时,使用checkACL进行权限的验证
                // todo  nodeRecord.acl 当前节点的acl
                // todo 跟进这个方法
                checkACL(zks, nodeRecord.acl, ZooDefs.Perms.ADMIN,
                        request.authInfo);
                version = setAclRequest.getVersion();
                currentVersion = nodeRecord.stat.getAversion();
                if (version != -1 && version != currentVersion) {
                    throw new KeeperException.BadVersionException(path);
                }
                version = currentVersion + 1;
                request.txn = new SetACLTxn(path, listACL, version);
                nodeRecord = nodeRecord.duplicate(request.hdr.getZxid());
                nodeRecord.stat.setAversion(version);
                addChangeRecord(nodeRecord);
                break;
            // todo     createSession/////////////////////////////////////////////////////////////////
            case OpCode.createSession:
            .
            .
            .


  • 先说一下有个亮点, 就是这个函数中倒数第二个参数位置写着需要的参数是record类型的,但是实际上我们传递进来的类型是SetACLRequest上面的这个空对象SetACLRequest这样的设计使得的扩展性变得超级强


这是record的类图



言归正传,来到这个函数算是进入了第二个高潮, 他主要做了这几件事

  • 检查session是否合法
  • 将数据反序列化进 SetACLRequest
  • 校验path是否合法
  • 去除重复的acl
  • CheckAcl鉴权

我们重点看最后两个地方


去除重复的acl#


fixupACL(request.authInfo, listACL)

这个函数很有趣,举个例子,通过控制台,我们连接上一个服务端,然后通过如下命令往服务端的authInfo集合中添加三条数据


addauth digest lisi1:1 
addauth digest lisi2:2
addauth digest lisi3:3


然后给lisi授予针对node1的权限


setAcl /node auth:lisi1:123123:adr


再次查看,会发现lisi2 lisi3同样有了对node1的权限


CheckAcl鉴权#


checkACL(zks, nodeRecord.acl, ZooDefs.Perms.ADMIN,request.authInfo); 源码如下:

这个函数的主要逻辑就是,从头到尾的执行,只要满足了合法的权限就退出,否则运行到最后都没有合法的权限,就抛出没有授权的异常从而中断请求,如果正常返回了,说明权限经过了验证,既然经过了验证request就可以继续在process链上运行,进一步进行处理


static void checkACL(ZooKeeperServer zks, List<ACL> acl, int perm,
            List<Id> ids) throws KeeperException.NoAuthException {
        // todo 这是个写在配置文件中的 配置属性 zookeeper.skipACL , 可以关闭acl验证
        if (skipACL) {
            return;
        }
        // todo 当前的节点没有任何验证的规则的话,直接通过
        if (acl == null || acl.size() == 0) {
            return;
        }
        // todo 如果ids中存放着spuer 超级用户,也直接通过
        for (Id authId : ids) {
            if (authId.getScheme().equals("super")) {
                return;
            }
        }
        // todo 循环当前节点上存在的acl点
        for (ACL a : acl) {
            Id id = a.getId();
            // todo 使用& 位运算  , 去ZooDefs类看看位移的情况
            // todo  如果设置的权限为 a.getPerms() =dra = d+r+a = 8+1+16 = 25
            // todo   perm = 16
            /**
             *  进行&操作
             *  25 & 16
             *  11001
             *  10000
             *   结果
             *  10000
             *  结果不是0 ,进入if { }
             */
            if ((a.getPerms() & perm) != 0) {
                if (id.getScheme().equals("world")
                        && id.getId().equals("anyone")) {
                    return;
                }
                AuthenticationProvider ap = ProviderRegistry.getProvider(id.getScheme());
                if (ap != null) {
                    for (Id authId : ids) {                        
                        if (authId.getScheme().equals(id.getScheme())
                                && ap.matches(authId.getId(), id.getId())) {
                            return;
                        }
                    }
                }
            }
        }
        //todo  到最后也没返回回去, 就抛出异常
        throw new KeeperException.NoAuthException();
    }


几个重要的参数

  • acl
  • 当前node已经存在的 需要的权限信息scheme:id;
  • perm
  • 当前用户的操作需要的权限
  • ids
  • 我们在上面通过addauth添加进authInfo列表中的信息
  • skip跳过权限验证


static boolean skipACL;
    static {
        skipACL = System.getProperty("zookeeper.skipACL", "no").equals("yes");
        if (skipACL) {
            LOG.info("zookeeper.skipACL==\"yes\", ACL checks will be skipped");
        }
    }


这里面在验证权限时存在位运算,prem在ZooDFS.java中维护


// todo 位移的操作
@InterfaceAudience.Public
public interface Perms {
    // 左移
    int READ = 1 << 0;   //1      2的0次方
    int WRITE = 1 << 1;    //2    2的1次方
    int CREATE = 1 << 2;   // 4
    int DELETE = 1 << 3;  // 8
    int ADMIN = 1 << 4;  // 16
    int ALL = READ | WRITE | CREATE | DELETE | ADMIN;  //31
    /**
     *      00001
     *      00010
     *      00100
     *      01000
     *      10000
     *
     *      结果11111 = 31
     *
     */
}


总结:#


通过跟踪上面的源码,我们知道了zookeeper的权限acl是如何实现的,以及客户端和服务端之间是如何相互配合的

  • 客户端同样是经过主线程跟进不同的命令类型,将请求打包packet发送到服务端
  • 服务端将addauth添加认证信息保存在内存中
  • node会被持久化,因为它需要的认证同样被持久化
  • 在进行处理request之前,会进行checkAcl的操作,它是在第一个处理器中完成的,只有经过权限认证,request才能继续在processor链中往下传递
相关实践学习
基于MSE实现微服务的全链路灰度
通过本场景的实验操作,您将了解并实现在线业务的微服务全链路灰度能力。
相关文章
|
6月前
|
存储 Shell 数据安全/隐私保护
ZooKeeper【基础知识 04】控制权限ACL(原生的 Shell 命令)
【4月更文挑战第11天】ZooKeeper【基础知识 04】控制权限ACL(原生的 Shell 命令)
100 7
|
6月前
|
存储 Shell 数据安全/隐私保护
ZooKeeper【基础 04】控制权限ACL(原生的 Shell 命令)
ZooKeeper【基础 04】控制权限ACL(原生的 Shell 命令)
161 0
|
算法 Shell Apache
Apache ZooKeeper - ZK的ACL权限控制( Access Control List )
Apache ZooKeeper - ZK的ACL权限控制( Access Control List )
512 0
|
Shell Linux 数据安全/隐私保护
Zookeeper系列(三)——Zookeeper的ACL权限控制
Zookeeper系列(三)——Zookeeper的ACL权限控制
668 1
Zookeeper系列(三)——Zookeeper的ACL权限控制
|
存储 中间件 数据安全/隐私保护
深入理解 ZooKeeper的ACL实现(一)
深入理解 ZooKeeper的ACL实现(一)
204 0
|
数据安全/隐私保护 安全 Linux
|
测试技术 Apache 开发工具
ZooKeeper 笔记(5) ACL(Access Control List)访问控制列表
zk做为分布式架构中的重要中间件,通常会在上面以节点的方式存储一些关键信息,默认情况下,所有应用都可以读写任何节点,在复杂的应用中,这不太安全,ZK通过ACL机制来解决访问权限问题,详见官网文档:http://zookeeper.
1004 0
|
2月前
|
安全 应用服务中间件 API
微服务分布式系统架构之zookeeper与dubbo-2
微服务分布式系统架构之zookeeper与dubbo-2