Using ACL with Curator

2019-04-03 22:34发布

问题:

Using CuratorFramework, could someone explain how I can:

  1. Create a new path
  2. Set data for this path
  3. Get this path

Using username foo and password bar? Those that don't know this user/pass would not be able to do anything.

I don't care about SSL or passwords being sent via plaintext for the purpose of this question.

回答1:

ACL in Apache Curator are for access control. Therefore, ZooKeeper do not provide any authentication mechanism like, clients who don't have correct password cannot connect to ZooKeeper or cannot create ZNodes. What it can do is, preventing unauthorized clients from accessing particular Znode/ZNodes. In order to do that, you have to setup CuratorFramework instance as I have described below. Remember, this will guarantee that, a ZNode create with a given ACL, can be again accessed by the same client or by a client presenting the same authentication information.

First you should build the CuratorFramework instane as follows. Here, the connectString means a comma separated list of ip and port combinations of the zookeeper servers in your ensemble.

CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
                .connectString(connectString)
                .retryPolicy(new ExponentialBackoffRetry(retryInitialWaitMs, maxRetryCount))
                .connectionTimeoutMs(connectionTimeoutMs)
                .sessionTimeoutMs(sessionTimeoutMs);
    /*
     * If authorization information is available, those will be added to the client. NOTE: These auth info are
     * for access control, therefore no authentication will happen when the client is being started. These
     * info will only be required whenever a client is accessing an already create ZNode. For another client of
     * another node to make use of a ZNode created by this node, it should also provide the same auth info.
     */
    if (zkUsername != null && zkPassword != null) {
        String authenticationString = zkUsername + ":" + zkPassword;
        builder.authorization("digest", authenticationString.getBytes())
                .aclProvider(new ACLProvider() {
                    @Override
                    public List<ACL> getDefaultAcl() {
                        return ZooDefs.Ids.CREATOR_ALL_ACL;
                    }

                    @Override
                    public List<ACL> getAclForPath(String path) {
                        return ZooDefs.Ids.CREATOR_ALL_ACL;
                    }
                });
    }

CuratorFramework client = builder.build();

Now you have to start it.

client.start();

Creating a path.

client.create().withMode(CreateMode.PERSISTENT).forPath("/your/ZNode/path");

Here, the CreateMode specify what type of a node you want to create. Available types are PERSISTENT,EPHEMERAL,EPHEMERAL_SEQUENTIAL,PERSISTENT_SEQUENTIAL,CONTAINER. Java Docs

If you are not sure whether the path up to /your/ZNode already exists, you can create them as well.

client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/your/ZNode/path");

Set Data

You can either set data when you are creating the ZNode or later. If you are setting data at the creation time, pass the data as a byte array as the second parameter to the forPath() method.

client.create().withMode(CreateMode.PERSISTENT).forPath("/your/ZNode/path","your data as String".getBytes());

If you are doing it later, (data should be given as a byte array)

client.setData().forPath("/your/ZNode/path",data);

Finally

I don't understand what you mean by get this path. Apache Curator is a java client (more than that with Curator Recipes) which use Apache Zookeeper in the background and hides edge cases and complexities of Zookeeper. In Zookeeper, they use the concept of ZNodes to store data. You can consider it as the Linux directory structure. All ZNodePaths should start with / (root) and you can go on specifying directory like ZNodePaths as you like. Ex: /someName/another/test/sample.

As shown in the above diagram, ZNode are organized in a tree structure. Every ZNode can store up to 1MB of data. Therefore, if you want to retrieve data stored in a ZNode, you need to know the path to that ZNode. (Just like you should know the table and column of a database in order to retrive data).

If you want to retrive data in a given path,

client.getData().forPath("/path/to/ZNode");

That's all you have to know when you want to work with Curator.

One more thing

ACL in Apache Curator are for access control. That is, if you set ACLProvider as follows,

new ACLProvider() {
    @Override
    public List<ACL> getDefaultAcl () {
        return ZooDefs.Ids.CREATOR_ALL_ACL;
    }

    @Override
    public List<ACL> getAclForPath (String path){
        return ZooDefs.Ids.CREATOR_ALL_ACL;
    }
}

only the client with the credentials identical to the creator will be given access to the corresponding ZNode later on. Autherization details are set as follows (See the client building example). There are other modes of ACL availble, like OPEN_ACL_UNSAFE which do not do any access control if you set it as the ACLProvider.

authorization("digest", authorizationString.getBytes())

they will be used later to control access to a given ZNode.

In short, if you want to prevent others from interfering your ZNodes, you can set the ACLProvider to return CREATOR_ALL_ACL and set the authorization to digest as shown above. Only the CuratorFramework instances using the same authorization string ("username:password") will be able to access those ZNodes. But it will not prevent others from creating ZNodes in paths which are not interfering with yours.

Hope you found what you want :-)



回答2:

It wasn't part of the original question, but I thought I would share a solution I came up with in which the credentials used determine the access level.

I didn't have much luck finding any examples and kept ending up on this page so maybe it will help someone else. I dug through the source code of Curator Framework and luckily the org.apache.curator.framework.recipes.leader.TestLeaderAcls class was there to point me in the right direction.

So in this example:

  1. One generic client used across multiple apps which only needs to read data from ZK.
  2. Another admin client has the ability to read, delete, and update nodes in ZK.
  3. Read-only or admin access is determined by the credentials used.

FULL-CONTROL ADMIN CLIENT

    import java.security.NoSuchAlgorithmException;
    import java.util.ArrayList;
    import java.util.List;
    import org.apache.curator.RetryPolicy;
    import org.apache.curator.framework.CuratorFramework;
    import org.apache.curator.framework.CuratorFrameworkFactory;
    import org.apache.curator.framework.api.ACLProvider;
    import org.apache.curator.retry.ExponentialBackoffRetry;
    import org.apache.zookeeper.ZooDefs;
    import org.apache.zookeeper.data.ACL;
    import org.apache.zookeeper.data.Id;
    import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;

    public class AdminClient {

        protected static CuratorFramework client = null;

        public void initializeClient() throws NoSuchAlgorithmException {
            String zkConnectString = "127.0.0.1:2181";
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
            final List<ACL> acls = new ArrayList<>();

            //full-control ACL
            String zkUsername = "adminuser";
            String zkPassword = "adminpass";
            String fullControlAuth = zkUsername + ":" + zkPassword;
            String fullControlDigest = DigestAuthenticationProvider.generateDigest(fullControlAuth);
            ACL fullControlAcl = new ACL(ZooDefs.Perms.ALL, new Id("digest", fullControlDigest));
            acls.add(fullControlAcl);

            //read-only ACL
            String zkReadOnlyUsername = "readuser";
            String zkReadOnlyPassword = "readpass";
            String readOnlyAuth = zkReadOnlyUsername + ":" + zkReadOnlyPassword;
            String readOnlyDigest = DigestAuthenticationProvider.generateDigest(readOnlyAuth);
            ACL readOnlyAcl = new ACL(ZooDefs.Perms.READ, new Id("digest", readOnlyDigest));
            acls.add(readOnlyAcl);

            //create the client with full-control access
            client = CuratorFrameworkFactory.builder()
                .connectString(zkConnectString)
                .retryPolicy(retryPolicy)
                .authorization("digest", fullControlAuth.getBytes())
                .aclProvider(new ACLProvider() {
                    @Override
                    public List<ACL> getDefaultAcl() {
                        return acls;
                    }

                    @Override
                    public List<ACL> getAclForPath(String string) {
                        return acls;
                    }
                })
                .build();
            client.start();
            //Now create, read, delete ZK nodes
        }
    }

READ-ONLY CLIENT

    import java.security.NoSuchAlgorithmException;
    import org.apache.curator.RetryPolicy;
    import org.apache.curator.framework.CuratorFramework;
    import org.apache.curator.framework.CuratorFrameworkFactory;
    import org.apache.curator.retry.ExponentialBackoffRetry;

    public class ReadOnlyClient {

        protected static CuratorFramework client = null;

        public void initializeClient() throws NoSuchAlgorithmException {
            String zkConnectString = "127.0.0.1:2181";
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
            String zkReadOnlyUsername = "readuser";
            String zkReadOnlyPassword = "readpass";
            String readOnlyAuth = zkReadOnlyUsername + ":" + zkReadOnlyPassword;
            client = CuratorFrameworkFactory.builder()
                    .connectString(zkConnectString)
                    .retryPolicy(retryPolicy)
                    .authorization("digest", readOnlyAuth.getBytes())
                    .build();
            client.start();
            //Now read ZK nodes
        }
    }