跳到主要内容

ACL 权限控制

ZooKeeper 使用 ACL(Access Control List)来控制对 znode 的访问权限。本章介绍 ACL 的概念和使用方法。

ACL 概述

ZooKeeper 的 ACL 机制类似于 Unix 文件权限,但更加灵活。每个 znode 都有一个 ACL 列表,定义了哪些用户可以执行哪些操作。

ACL 结构

每个 ACL 条目由两部分组成:

scheme:id:permissions

scheme: 认证方案(如 world, auth, digest, ip)
id: 标识符(具体含义取决于 scheme)
permissions: 权限组合

权限类型

ZooKeeper 定义了以下权限:

权限缩写说明
READr读取节点数据和子节点列表
WRITEw写入节点数据
CREATEc创建子节点
DELETEd删除子节点
ADMINa设置 ACL
权限组合示例:
- cdrwa: 所有权限
- r: 只读
- rw: 读写
- rwcda: 所有权限(完整写法)

认证方案(Scheme)

1. world 方案

world 方案只有一个 id:anyone,表示所有用户。

# 设置 world 方案的 ACL
setAcl /node world:anyone:rwcda

# 这是最开放的权限,任何人都可以执行所有操作

Java 示例

import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooDefs.Perms;

public class WorldAclDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);

// 使用预定义的 OPEN_ACL_UNSAFE(world:anyone:cdrwa)
zk.create("/open-node", "data".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

// 使用预定义的 READ_ACL_UNSAFE(world:anyone:r)
zk.create("/read-only", "data".getBytes(),
Ids.READ_ACL_UNSAFE, CreateMode.PERSISTENT);

zk.close();
}
}

2. auth 方案

auth 方案表示当前已认证的用户,不需要指定具体的 id。

# 先添加认证信息
addauth digest user1:password1

# 使用 auth 方案设置 ACL
setAcl /node auth::rwcda

# auth 方案会自动使用当前认证的用户

Java 示例

import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.ACL;

public class AuthAclDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);

// 添加认证
zk.addAuthInfo("digest", "user1:password1".getBytes());

// 创建节点时使用 auth 方案
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(Perms.ALL, Ids.AUTH_IDS));

zk.create("/auth-node", "data".getBytes(), acls, CreateMode.PERSISTENT);

zk.close();
}
}

3. digest 方案

digest 方案使用用户名和密码进行认证,格式为 username:password

# 使用 digest 方案设置 ACL
# id 格式:username:base64(SHA1(password))
setAcl /node digest:user1:XDkd2dsEuhfe9dsf=:(rwcda)

# 客户端需要先认证才能访问
addauth digest user1:password1

Java 示例

import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
import java.security.NoSuchAlgorithmException;

public class DigestAclDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);

// 生成 digest id
String username = "admin";
String password = "admin123";
String digestId = DigestAuthenticationProvider.generateDigest(username + ":" + password);

// 创建 ACL
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(Perms.ALL, new Id("digest", digestId)));

// 创建节点
zk.create("/secure-node", "data".getBytes(), acls, CreateMode.PERSISTENT);

// 访问前需要认证
zk.addAuthInfo("digest", (username + ":" + password).getBytes());

// 现在可以访问
byte[] data = zk.getData("/secure-node", false, null);
System.out.println("Data: " + new String(data));

zk.close();
}
}

4. ip 方案

ip 方案使用客户端 IP 地址进行认证。

# 允许特定 IP 访问
setAcl /node ip:192.168.1.100:rwcda

# 允许 IP 段访问(CIDR 表示法)
setAcl /node ip:192.168.1.0/24:rwcda

Java 示例

public class IpAclDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);

// 创建 IP 方案的 ACL
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(Perms.ALL, new Id("ip", "192.168.1.0/24")));
acls.add(new ACL(Perms.READ, new Id("ip", "10.0.0.0/8")));

zk.create("/ip-protected", "data".getBytes(), acls, CreateMode.PERSISTENT);

zk.close();
}
}

5. x509 方案

x509 方案使用 SSL 客户端证书进行认证。

# 使用 x509 证书的 Subject DN 作为 id
setAcl /node x509:CN=admin,O=MyOrg,C=CN:rwcda

预定义 ACL

ZooKeeper 提供了一些预定义的 ACL:

import org.apache.zookeeper.ZooDefs.Ids;

// 完全开放(world:anyone:cdrwa)
Ids.OPEN_ACL_UNSAFE

// 只读(world:anyone:r)
Ids.READ_ACL_UNSAFE

// 创建者所有权限(auth::cdrwa)
Ids.CREATOR_ALL_ACL

使用示例

public class PredefinedAclDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);

// 完全开放
zk.create("/public", "data".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

// 只读
zk.create("/readonly", "data".getBytes(),
Ids.READ_ACL_UNSAFE, CreateMode.PERSISTENT);

// 创建者所有权限(需要先认证)
zk.addAuthInfo("digest", "admin:admin123".getBytes());
zk.create("/private", "data".getBytes(),
Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);

zk.close();
}
}

命令行操作 ACL

查看 ACL

# 获取节点的 ACL
[zk: localhost:2181(CONNECTED) 0] getAcl /node
'world,'anyone
: cdrwa

# 查看带认证的节点 ACL
[zk: localhost:2181(CONNECTED) 1] getAcl /secure-node
'digest,'admin:XDkd2dsEuhfe9dsf=
: cdrwa

设置 ACL

# 设置 world 方案
[zk: localhost:2181(CONNECTED) 2] setAcl /node world:anyone:rwa

# 设置 digest 方案
[zk: localhost:2181(CONNECTED) 3] setAcl /secure-node digest:admin:XDkd2dsEuhfe9dsf=:cdrwa

# 设置多个 ACL
[zk: localhost:2181(CONNECTED) 4] setAcl /node world:anyone:r,ip:192.168.1.100:rwcda

添加认证

# 添加 digest 认证
[zk: localhost:2181(CONNECTED) 5] addauth digest admin:admin123

# 添加后可以访问受保护的节点
[zk: localhost:2181(CONNECTED) 6] get /secure-node
data

Java API 操作 ACL

创建带 ACL 的节点

import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.ZooDefs.Perms;

public class CreateWithAclDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);

// 构建自定义 ACL
List<ACL> acls = new ArrayList<>();

// 管理员:所有权限
acls.add(new ACL(Perms.ALL, new Id("digest",
DigestAuthenticationProvider.generateDigest("admin:admin123"))));

// 只读用户:只读权限
acls.add(new ACL(Perms.READ, new Id("digest",
DigestAuthenticationProvider.generateDigest("reader:reader123"))));

// 特定 IP:读写权限
acls.add(new ACL(Perms.READ | Perms.WRITE, new Id("ip", "192.168.1.100")));

// 创建节点
zk.create("/custom-acl", "data".getBytes(), acls, CreateMode.PERSISTENT);

zk.close();
}
}

获取和修改 ACL

import org.apache.zookeeper.data.Stat;

public class AclManagementDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);
zk.addAuthInfo("digest", "admin:admin123".getBytes());

// 获取 ACL
Stat stat = new Stat();
List<ACL> acls = zk.getACL("/custom-acl", stat);

System.out.println("当前 ACL:");
for (ACL acl : acls) {
System.out.println(" " + acl.getId().getScheme() + ":" +
acl.getId().getId() + " -> " + acl.getPerms());
}

// 修改 ACL(需要 ADMIN 权限)
List<ACL> newAcls = new ArrayList<>();
newAcls.add(new ACL(Perms.ALL, new Id("digest",
DigestAuthenticationProvider.generateDigest("admin:admin123"))));

zk.setACL("/custom-acl", newAcls, stat.getAversion());

zk.close();
}
}

认证操作

public class AuthenticationDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);

// 添加认证信息
zk.addAuthInfo("digest", "admin:admin123".getBytes());

// 现在可以访问受保护的节点
try {
byte[] data = zk.getData("/secure-node", false, null);
System.out.println("Data: " + new String(data));
} catch (KeeperException.NoAuthException e) {
System.out.println("认证失败或权限不足");
}

zk.close();
}
}

权限检查流程

┌─────────────────────────────────────────────────────────────┐
│ 权限检查流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 客户端请求 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 1. 检查会话是否有效 │ │
│ └─────────────────┬───────────────────┘ │
│ │ │
│ ┌─────────────┴─────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ 会话有效 会话无效 │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 2. 获取客户端 │ │ 返回会话过期错误 │ │
│ │ 认证信息 │ └─────────────────┘ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 3. 获取目标节点的 ACL 列表 │ │
│ └─────────────────┬───────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 4. 遍历 ACL,匹配认证信息 │ │
│ └─────────────────┬───────────────────┘ │
│ │ │
│ ┌─────────────┴─────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ 匹配成功 无匹配 │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 5. 检查权限位 │ │ 返回权限不足错误 │ │
│ │ 是否满足 │ └─────────────────┘ │
│ └────────┬────────┘ │
│ │ │
│ ┌────┴────┐ │
│ │ │ │
│ ▼ ▼ │
│ 权限足够 权限不足 │
│ │ │ │
│ ▼ ▼ │
│ 执行操作 返回权限不足错误 │
│ │
└─────────────────────────────────────────────────────────────┘

ACL 最佳实践

1. 最小权限原则

只授予必要的权限:

// 不推荐:授予过多权限
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(Perms.ALL, Ids.ANYONE_ID_UNSAFE));

// 推荐:只授予必要权限
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(Perms.READ, Ids.ANYONE_ID_UNSAFE)); // 只读
acls.add(new ACL(Perms.ALL, new Id("digest", adminDigest))); // 管理员

2. 分离读写权限

// 读写分离
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(Perms.READ, readerId)); // 读者
acls.add(new ACL(Perms.READ | Perms.WRITE, writerId)); // 写者
acls.add(new ACL(Perms.ALL, adminId)); // 管理员

3. 使用认证保护敏感数据

// 敏感数据使用 digest 认证
List<ACL> secureAcls = new ArrayList<>();
secureAcls.add(new ACL(Perms.ALL, new Id("digest",
DigestAuthenticationProvider.generateDigest("admin:securePassword"))));

zk.create("/sensitive-config", secretData.getBytes(),
secureAcls, CreateMode.PERSISTENT);

4. IP 白名单

// 内网访问控制
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(Perms.ALL, new Id("ip", "10.0.0.0/8")));
acls.add(new ACL(Perms.ALL, new Id("ip", "192.168.0.0/16")));

5. 定期审计 ACL

public void auditAcls(ZooKeeper zk, String path) throws Exception {
List<String> children = zk.getChildren(path, false);

for (String child : children) {
String childPath = path + "/" + child;
List<ACL> acls = zk.getACL(childPath, new Stat());

System.out.println("Path: " + childPath);
for (ACL acl : acls) {
System.out.println(" ACL: " + acl.getId().getScheme() +
":" + acl.getId().getId() +
" -> " + acl.getPerms());
}

// 递归检查子节点
auditAcls(zk, childPath);
}
}

常见问题

1. NoAuthException

// 原因:未认证或权限不足
try {
byte[] data = zk.getData("/secure-node", false, null);
} catch (KeeperException.NoAuthException e) {
// 解决方案:添加认证
zk.addAuthInfo("digest", "admin:admin123".getBytes());
}

2. BadVersionException

// 原因:ACL 版本号不匹配
try {
zk.setACL("/node", newAcls, -1); // -1 表示不检查版本
} catch (KeeperException.BadVersionException e) {
// 获取最新版本号后重试
Stat stat = new Stat();
zk.getACL("/node", stat);
zk.setACL("/node", newAcls, stat.getAversion());
}

3. ACL 继承问题

// ZooKeeper 的 ACL 不继承
// 子节点需要单独设置 ACL

// 父节点 ACL
zk.setACL("/parent", parentAcls, -1);

// 子节点需要单独设置
zk.create("/parent/child", data, childAcls, CreateMode.PERSISTENT);

小结

本章学习了 ZooKeeper 的 ACL 权限控制:

  1. ACL 结构:scheme:id:permissions 格式
  2. 权限类型:READ、WRITE、CREATE、DELETE、ADMIN
  3. 认证方案:world、auth、digest、ip、x509
  4. 预定义 ACL:OPEN_ACL_UNSAFE、READ_ACL_UNSAFE、CREATOR_ALL_ACL
  5. 最佳实践:最小权限、读写分离、认证保护、IP 白名单

下一章我们将学习如何使用 Java 客户端开发 ZooKeeper 应用。