ZooKeeper x509 certificates ACL

The documentation states that setting an ACL via the ZooKeeper CLI usually works like this:

setAcl /your/node scheme:authenticated_id:permissions

In the case of the x509 scheme, the Authenticated ID for the user is the DN string of their certificate. In confirmation of this, when a user connects via a certificate, there is a line in the server log:

INFO Authenticated Id 'CN=hostname,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown' for Scheme 'x509' (org.apache.zookeeper.server.auth.X509AuthenticationProvider)

Now, following the syntax of the setAcl command, we try to add a new entry:

setAcl /your/node x509:CN=hostname,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown:cdrwa

And we get errors:

OU=Unknown does not have the form scheme:id:perm
O=Unknown does not have the form scheme:id:perm
L=Unknown does not have the form scheme:id:perm
ST=Unknown does not have the form scheme:id:perm
C=Unknown:cdrwa does not have the form scheme:id:perm
Acl is not valid : /your/node

This is a bug. In ZooKeeper CLI you can add several ACL entries by one command, and they are separated by commas. But our Autenticated ID also has commas, and the ZooKeeper CLI treats them as different ACL entries, not one.

Because of this bug there is no way to set the x509 ACL via certificates in the ZooKeeper CLI at all. You must use the Java API.

In pom.xml, add the Maven dependency for the ZooKeeper Client. For example, version 3.5.8:

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.5.8</version>
</dependency>

Now let’s create the ZooKeeperACL class:

import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;

import java.util.Collections;
import java.util.concurrent.CountDownLatch;

public class ZooKeeperACL {

    private final CountDownLatch connSignal = new CountDownLatch(0);

    private static final String path = "/your/node";

    private static final String scheme = "x509";

    private static final String authenticatedId = "CN=hostname,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown";

    private static final String permissions = "cdrwa";

    public static void main(String[] args) throws Exception {
        ZooKeeperACL zooKeeperACL = new ZooKeeperACL();
        zooKeeperACL.setX509Acl();
    }
    
    public void setX509Acl() throws Exception {
        ZooKeeper zooKeeper = makeZooKeeper();
        int aVersion = zooKeeper.exists(path, true).getAversion();
        ACL acl = prepareAcl(scheme, authenticatedId, permissions);
        zooKeeper.setACL(path, Collections.singletonList(acl), aVersion);
    }

    public ZooKeeper makeZooKeeper() throws Exception {
        String host = "localhost:2181";
        ZooKeeper zooKeeper = new ZooKeeper(host, 3000, event -> {
            if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
                connSignal.countDown();
            }
        });
        connSignal.await();
        return zooKeeper;
    }

    public ACL prepareAcl(String scheme, String authenticatedId, String permissions) {
        ACL acl = new ACL();
        Id id = new Id();
        id.setScheme(scheme);
        id.setId(authenticatedId);
        acl.setId(id);
        acl.setPerms(getPermFromString(permissions));
        return acl;
    }

    public int getPermFromString(String permString) {
        int perm = 0;
        for (int i = 0; i < permString.length(); i++) {
            switch (permString.charAt(i)) {
                case 'r':
                    perm |= ZooDefs.Perms.READ;
                    break;
                case 'w':
                    perm |= ZooDefs.Perms.WRITE;
                    break;
                case 'c':
                    perm |= ZooDefs.Perms.CREATE;
                    break;
                case 'd':
                    perm |= ZooDefs.Perms.DELETE;
                    break;
                case 'a':
                    perm |= ZooDefs.Perms.ADMIN;
                    break;
                default:
                    System.err.println("Unknown perm type: " + permString.charAt(i));
            }
        }
        return perm;
    }
}

Just insert the required data into fields: path, scheme, authenticatedId and permissions, and run the ZooKeeperACL class.

Telegram channel

If you still have any questions, feel free to ask me in the comments under this article or write me at promark33@gmail.com.

If I saved your day, you can support me 🤝

Leave a Reply

Your email address will not be published. Required fields are marked *