If you have one or more Zookeeper "multi-tenant" clusters you may want to protect znodes against unwanted modifications.
Here is a very simple and short introduction to the ACL and custom authentication features.
Complete source code with JUnit test is available here :
Once it is done you must tell Zookeeper server to use it, this can be done by setting the Java system property zookeeper.authProvider.x (where x is an integer)
You can do this in a startup script but we want to use a JUnit test :
Register you ACL provider using the Curator client builder :
Add client authentication data using the previous builder :
Complete JUnit test with a Zookeeper testing server using the Curator framework is available here
Here is a very simple and short introduction to the ACL and custom authentication features.
This post is not intended to give you best practices about security and Zookeeper, the only goal is to give you a complete example of a custom authentication handler.
Complete source code with JUnit test is available here :
https://github.com/barkbay/zookeeper-acl-sample/
Use case
Let say that your Zookeeper cluster is used by several users. In order to restrict user actions you have decided that each user must prefix all paths with the first letter of his name.- User foo is only allowed to create, read, delete and update znodes under the /f znode.
- User bar is only allowed to create, read, delete and update znodes under the /b znode.
Get client authentication data on the server side
Zookeeper client authentication can be easily customized , all you have to do is to create a class that extends org.apache.zookeeper.server.auth.AuthenticationProvider :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CustomUserAuthenticationProvider | |
implements AuthenticationProvider { | |
@Override | |
public String getScheme() { | |
// The name of the scheme as it is provided by client | |
return "user"; | |
} | |
@Override | |
public Code handleAuthentication(ServerCnxn cnxn, byte[] authData) { | |
final String userName = new String(authData, Charsets.UTF_8); | |
// A non null or empty user name must be provided | |
if (!Strings.isNullOrEmpty(userName)) { | |
// This line is VERY important ! return code | |
// is not enough ! | |
cnxn.addAuthInfo(new Id(getScheme(), userName)); | |
return Code.OK; | |
} | |
return Code.AUTHFAILED; | |
} | |
@Override | |
public boolean matches(String id, String aclExpr) { | |
if (Strings.isNullOrEmpty(id) || Strings.isNullOrEmpty(aclExpr)) { | |
return false; | |
} | |
// Check if the first letter of the user name | |
// match the one in the acl | |
return id.charAt(0) == aclExpr.charAt(0); | |
} | |
@Override | |
public boolean isValid(String id) { | |
// A valid user name is at least 1 char length | |
return !Strings.isNullOrEmpty(id) && id.length() == 1; | |
} | |
} |
You can do this in a startup script but we want to use a JUnit test :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class BaseClassForSecurityTests { | |
@Before | |
public void setup() throws Exception { | |
// Start a Zookeeper unit test server | |
System.setProperty(DebugUtils.PROPERTY_DONT_LOG_CONNECTION_ISSUES, "true"); | |
// Register our custom authentication provider | |
System.setProperty("zookeeper.authProvider.1", | |
"morello.zk.acl.CustomUserAuthenticationProvider"); | |
// Ok, create a testing Zookeeper server with Curator | |
server = new TestingServer(); | |
} | |
@After | |
public void teardown() throws Exception { | |
System.clearProperty("zookeeeper.authProvider.1"); | |
server.close(); | |
} | |
} |
Let Zookeeper client automatically set ACLs with Curator
Curator helps you to automatically set right ACLs on znodes. This can be done providing an ACLProvider implementation :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ACLProvider implements | |
com.netflix.curator.framework.api.ACLProvider { | |
@Override | |
public List<ACL> getAclForPath(String path) { | |
// The first letter of the path is the ACL id | |
final String firstLetter = String.valueOf(path.charAt(1)); | |
final Id FIRST_USER_LETTER = new Id("user", firstLetter); | |
// Create a new ACL with the first letter of the path as | |
// an ID and give all permissions for users | |
ACL acl = new ACL(Perms.ALL, FIRST_USER_LETTER); | |
return Collections.singletonList(acl); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protected CuratorFrameworkFactory.Builder getClientBuilder() { | |
// Create a client builder | |
CuratorFrameworkFactory.Builder clientBuilder = | |
CuratorFrameworkFactory.builder() | |
.connectString(server.getConnectString()) | |
.retryPolicy(new RetryOneTime(2000)) | |
.aclProvider(new ACLProvider()); | |
return clientBuilder; | |
} |
Add client authentication data using the previous builder :
Comments
Post a Comment