4.5 ACL 权限控制:案卷的访问登记簿 本节摘要:每个 ZNode 都挂着一张访问控制表:谁(scheme 加 id)能对它做什么(权限位)。本节讲清五种认证方式、七类权限位、父节点权限对子节点的实际影响,以及共享集群上最常用的 digest 配方与常见翻车现场。 一张容易漏办的保险 多应用共享一个 ZooKeeper 集群是常态,但默认配置下任何人都能读写任何节点——只要能连上 2181 端口。真实事故的形状通常是:两个团队共用集群,一个应用的清理脚本把路径写宽了半个字符,删光了另一个应用的配置树。ACL(Access Control List)就是防这口的锁:它在节点级声明访问资格,谁能在哪个卷宗上做什么,登记在案。 先立三个认知坐标。
本节摘要:每个 ZNode 都挂着一张访问控制表:谁(scheme 加 id)能对它做什么(权限位)。本节讲清五种认证方式、七类权限位、父节点权限对子节点的实际影响,以及共享集群上最常用的 digest 配方与常见翻车现场。
多应用共享一个 ZooKeeper 集群是常态,但默认配置下任何人都能读写任何节点——只要能连上 2181 端口。真实事故的形状通常是:两个团队共用集群,一个应用的清理脚本把路径写宽了半个字符,删光了另一个应用的配置树。ACL(Access Control List)就是防这口的锁:它在节点级声明访问资格,谁能在哪个卷宗上做什么,登记在案。
先立三个认知坐标。其一,ACL 不提供用户体系,它只是"认证方式加资格表"的组合,认证靠 scheme 在建连或操作时完成。其二,ACL 不从父节点继承——/a 设了只读,/a/b 依然按自己表执行;只是创建子节点需要父节点的创建权限,读写时各看各表。其三,权限校验发生在受理服务器本地,属轻量操作,不会成为性能瓶颈。
**认证方式(scheme)**五种常用:world(所有人,唯一 id 是 anyone)、digest(用户名密码,用 SHA1 加密摘要存储)、auth(已登录的当前用户)、ip(来源地址段)、super(超级管理员,越过一切校验)。权限位七类:CREATE、DELETE、READ、WRITE、ADMIN(能改 ACL 本身)五大件,外加 ALL(全权)与 READ 以外都算的写族。日常组合记住一个口诀:读共享用 READ 广开,写收紧到 digest。
zkCli 的 addauth 与 setAcl 组合是日常主力。目标效果:/app-a 子树只有 user-a 能写,任何人可读:
$ zkCli.sh -server 127.0.0.1:2181 # 1. 先在会话里声明身份(后续该会话的写操作都以此身份认证) [zk] addauth digest user-a:pass-a # 2. 创建节点并直接指定 ACL:只有 user-a 可读写,world 众人只读 [zk] create /app-a "secret" [zk] setAcl /app-a digest:user-a:加密串:cdwra, world:anyone:r [zk] getAcl /app-a 'digest,'user-a : cdrwa 'world,'anyone : r # 3. 未认证会话的体验:读得到、写不进 $ zkCli.sh -server 127.0.0.1:2181 [zk] get /app-a secret # READ 对 world 开放 [zk] set /app-a "hacked" Authentication is not valid : /app-a # 拒绝
编程侧同理,Curator 在 builder 与每次操作上都能挂 ACLProvider。注意密码传的是明文,框架与命令行都会算成摘要存储:
// Curator 侧:会话级身份声明 + 创建时挂 ACL client = CuratorFrameworkFactory.builder() .connectString("127.0.0.1:2181") .authorization("digest", "user-a:pass-a".getBytes()) // 等价 addauth .retryPolicy(new ExponentialBackoffRetry(1000, 3)) .build(); client.start(); // 创建时直接带 ACL:digest 身份全权,匿名只读 List<ACL> acls = new ArrayList<>(); acls.add(new ACL(ZooDefs.Perms.ALL, new Id("digest", DigestAuthenticationProvider.generateDigest("user-a:pass-a")))); acls.add(new ACL(ZooDefs.Perms.READ, ZooDefs.Ids.ANYONE_ID_UNSAFE)); client.create().creatingParentsIfNeeded() .withACL(acls) .forPath("/app-a/config/db", "jdbc:demo".getBytes());
ip 方式适合内网机器对机器的信任:setAcl /ops ip:10.0.0.0/24:cdra 把运维子树限制在网段内。但别对它有过高期待——地址可伪造,它防误操作有余、防恶意不足。
现场一:锁死在自己门外。 给 /app-a 只留了 digest 身份,会话重启忘了 addauth,读全报 NoAuth。自救路径:用超级管理员身份连入(服务端启动参数里配置 super scheme 的账号密码),或该节点无子节点时由有权限的会话重建。预防:改动 ACL 前先用当前会话跑一遍关键读写。
现场二:以为权限会继承。 只给 /app-a 设了 ACL,之后创建的 /app-a/config 用的是创建会话的默认表(world 全开)。ACLEveryone 看似设防实则漏风。预防:把 ACLProvider 挂在客户端 builder 上,让所有创建动作自动带表。
现场三:删不掉、读不了的"幽灵节点"。 前一个应用的会话用某 digest 建了节点后下线,新会话既无身份又无 ADMIN 权限,连 getAcl 都看不到。自救:super 账号接管;预防:共享集群的节点命名规范里加一条——每个应用只在自己 namespace 子树内活动(3.3 节的 namespace 参数正是为此)。
# 验证 ghost 节点:无权限会话的视角 [zk] getAcl /ghost-node Authentication is not valid : /ghost-node # 连表都看不到,因为没有 READ 与 ADMIN # super 账号连入后接管: # 服务端环境变量 -Dzookeeper.DigestAuthenticationProvider.superDigest=super:加密串 [zk] addauth digest super:super-pass [zk] setAcl /ghost-node world:anyone:cdrwa # 夺回控制权,按需重设
问:有没有节点的全局管理后台能看全部 ACL? 没有现成的。ACL 按"有无资格"隐藏信息,无 READ 权限的会话连节点存在与否都不可见。运维盘点要靠 super 账号遍历,或定期用脚本以各应用身份自检。
第四章五个案件全部审结。下一章带着这些机制去现场:四个经典判例逐一开庭。