zookeeper – security and acl (access control list)

ZooKeeper uses ACLs (Access Control List) to control access to its znodes (the data nodes of a ZooKeeper data tree).

The ACL implementation is quite similar to UNIX file access permissions: it employs permission bits to allow/disallow various operations against a node and the scope to which the bits apply.

Unlike standard UNIX permissions, a ZooKeeper node is not limited by the three standard scopes for user (owner of the file), group, and world (other). ZooKeeper does not have a notion of an owner of a znode. Instead, an ACL specifies sets of ids and permissions that are associated with those ids.

ZooKeeper supports pluggable authentication schemes. Ids are specified using the form scheme:id, where scheme is a the authentication scheme that the id corresponds to. For example, hostId:host.corp.com is an id for a host named host.corp.com.

When a client connects to ZooKeeper and authenticates itself, ZooKeeper associates all the ids that correspond to a client with the clients connection. These ids are checked against the ACLs of znodes when a clients tries to access a node. ACLs are made up of pairs of (scheme:expression, perms). The format of the expression is specific to the scheme. For example, the pair (ip:19.22.0.0/16, READ) gives the READ permission to any clients with an IP address that starts with 19.22.

ACL Permissions

ZooKeeper supports the following permissions:

  • CREATE - you can create a child node
  • READ - you can get data from a node and list its children.
  • WRITE - you can set data for a node
  • DELETE - you can delete a child node
  • ADMIN - you can set permissions

The CREATE and DELETE permissions have been broken out of the WRITE permission for finer grained access controls. The cases for CREATE and DELETE are the following:

You want A to be able to do a set on a ZooKeeper node, but not be able to CREATE or DELETE children.

CREATE without DELETE: clients create requests by creating ZooKeeper nodes in a parent directory. You want all clients to be able to add, but only request processor can delete. (This is kind of like the APPEND permission for files.)

Also, the ADMIN permission is there since ZooKeeper doesn’t have a notion of file owner. In some sense the ADMIN permission designates the entity as the owner. ZooKeeper doesn’t support the LOOKUP permission (execute permission bit on directories to allow you to LOOKUP even though you can’t list the directory). Everyone implicitly has LOOKUP permission. This allows you to stat a node, but nothing more. (The problem is, if you want to call zoo_exists() on a node that doesn’t exist, there is no permission to check.)

Built-in ACL Schemes

ZooKeeeper has the following built in schemes:

  • world has a single id, anyone, that represents anyone.
  • auth doesn’t use any id, represents any authenticated user.
  • digest uses a username:password string to generate MD5 hash which is then used as an ACL ID identity. Authentication is done by sending the username:password in clear text. When used in the ACL the expression will be the username:base64 encoded SHA1 password digest.
  • host uses the client host name as an ACL ID identity. The ACL expression is a hostname suffix. For example, the ACL expression host:corp.com matches the ids host:host1.corp.com and host:host2.corp.com, but not host:host1.store.com.
  • ip uses the client host IP as an ACL ID identity. The ACL expression is of the form addr/bits where the most significant bits of addr are matched against the most significant bits of the client host IP.

Example

Here’s an example of creating a curator framework client and setting both no auth and a specified ACL.

1
2
3
4
5
6
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
.connectString(zkConnectionString)
.sessionTimeoutMs(sessionTimeout)
.connectionTimeoutMs(connectionTimeout)
.retryPolicy(retryPolicy)
.aclProvider(authProvider.getAclProvider());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class  implements IZkAuthProvider {

public ACLProvider getAclProvider() {
return new ACLProvider() {

* @see org.apache.curator.framework.api.ACLProvider#getDefaultAcl()
*/

public List<ACL> getDefaultAcl() {
return ZooDefs.Ids.OPEN_ACL_UNSAFE;
}


* @see org.apache.curator.framework.api.ACLProvider#getAclForPath(java.lang.String)
*/

public List<ACL> getAclForPath(String path) {
return ZooDefs.Ids.OPEN_ACL_UNSAFE;
}
};
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class DigestAuthProvider implements ACLProvider, IZkAuthProvider {

private String zkUser;
private String zkPassword;
private List<ACL> acls;


public List<ACL> getDefaultAcl() {
MessageDigest md = MessageDigest.getInstance("SHA1");

// note: despite what the docs at http://zookeeper.apache.org/doc/r3.4.6/zookeeperProgrammers.html
// say, the ACL expression is
// digest, user:base64(sha1(user:password))
// and *NOT*
// digest, user:base64(sha1(password))
String idPassword = String.format("%s:%s", zkUser, zkPassword);
byte[] authBytes = idPassword.getBytes();

byte[] digestBytes = md.digest(authBytes);
String encodedBytes = Base64.encodeBase64String(digestBytes);

String id = String.format("%s:%s", zkUser, encodedBytes);
ACL acl = new ACL(ZooDefs.Perms.ALL, new Id("digest", id));
acls = Collections.singletonList(acl);
return this;
}
}