javaaclapache-zookeeperdigest-authentication

org.apache.zookeeper.KeeperException$InvalidACLException: KeeperErrorCode = InvalidACL for /f


I am working with zookeeper 3.4.6, I'm using acl in order to authenticate with zookeeper server. I have my own implementation ZooKeeperSupport, it's a support for create, remove and verify znode. I am triying to create a znode using acl, but fail throwning InvalidACLException in this part of the code zooKeeperSupport.create("/f", DATA_F);

I'm basing this project to do it zookeeper-acl-sample, but I want to use digest auth because use user and password

BasicMockZookeeperSecurity

public class BasicMockZookeeperSecurity {

@Resource (name = "zooKeeperSupportFactory")
protected ZooKeeperSupportFactory zooKeeperSupportFactory;

public static final byte[] DATA_F = "data for znode /f".getBytes(Charsets.UTF_8);
public static final byte[] DATA_B = "data for znode /b".getBytes(Charsets.UTF_8);

@Before
public void setup() throws Exception {

    System.setProperty("curator-dont-log-connection-problems", "true");
    System.setProperty("zookeeper.security.user", "user");
    System.setProperty("zookeeper.security.password", "pass");
    System.setProperty("zookeeper.authProvider.1","com.e.product.fraud.common.zookeeper.security.CustomUserAuthenticationProvider");

    zooKeeperSupport = CuratorFrameworkFactory.builder().connectString(connectionPath).retryPolicy(retryPolicy).aclProvider(new ACLProvider()).build();
    zooKeeperSupport.create("/f", DATA_F);
    zooKeeperSupport.create("/b", DATA_B);
}

}

ZookeeperSecurtyTest

public class ZookeeperSecurtyTest extends BasicMockZookeeperSecurity {

@Test
public void securityTester() throws Exception {

    final CuratorFramework curatorFramework = CuratorFrameworkFactory.builder().connectString(connectionPath).retryPolicy(retryPolicy).aclProvider(new ACLProvider()).authorization(ZookeeperSecurityUtil.getCredentialsFromSystemProperties()).build();
    curatorFramework.getData().forPath("/f");

}

@After
public void teardown() throws Exception {
    System.clearProperty("zookeeeper.authProvider.1");
    System.clearProperty("zookeeper.security.user");
    System.clearProperty("zookeeper.security.password");
}

}

ZooKeeperSupport

@SuppressWarnings ("unchecked")
public class ZooKeeperSupport implements AutoCloseable {

private CuratorFramework curatorClient;

private Builder builder;

public ZooKeeperSupport(final Builder builder) {
    this.setBuilder(builder);
    this.curatorClient = builder.build();
}

public <T> T getData(final String path) throws Exception {
    startConnection();

    return (T) curatorClient.getData().forPath(path);
}

public <T> T create(final String path, final byte[] data) throws Exception {
    startConnection();

    return (T) curatorClient.create().forPath(path, data);
}

public <T> T create(final String path) throws Exception {
    startConnection();

    return (T) curatorClient.create().forPath(path);
}

public <T> T delete(final String path) throws Exception {
    startConnection();

    return (T) curatorClient.delete().forPath(path);
}

public boolean exists(final String path) throws Exception {
    startConnection();

    final Stat stat = curatorClient.checkExists().forPath(StringUtils.trim(path));

    return stat != null ? true : false;
}

public void startConnection() {
    if (curatorClient.getState() == CuratorFrameworkState.LATENT) {
        curatorClient.start();
    }
}

public List<String> getChildrenNames(final String path) throws Exception {
    startConnection();

    return curatorClient.getChildren().forPath(path);
}

@Override
public void close() throws Exception {
    curatorClient.close();
    curatorClient = null;
}

public Builder getBuilder() {
    return builder;
}

public void setBuilder(final Builder builder) {
    this.builder = builder;
}

public CuratorFramework getCuratorClient() {
    return curatorClient;
}

public void setCuratorClient(final CuratorFramework curatorClient) {
    this.curatorClient = curatorClient;
}

}

ZookeeperSecurityUtil

public class ZookeeperSecurityUtil {

public static List<AuthInfo> getCredentialsFromSystemProperties() {
    final List<AuthInfo> authInfo = new ArrayList<>();

    final String user = System.getProperty("zookeeper.security.user");
    final String password = System.getProperty("zookeeper.security.password");

    authInfo.add(new AuthInfo("digest", new String(user + ":" + password).getBytes(Charsets.UTF_8)));

    return authInfo;

}
}

CustomUserAuthenticationProvider

public class CustomUserAuthenticationProvider implements AuthenticationProvider {

@Override
public String getScheme() {
    return "digest";
}

@Override
public Code handleAuthentication(final ServerCnxn cnxn, final byte[] authData) {
    final String userName = new String(authData, Charsets.UTF_8);
    if (StringUtils.isNotBlank(userName)) {
        cnxn.addAuthInfo(new Id(getScheme(), userName));
        return Code.OK;
    }
    return Code.AUTHFAILED;
}

@Override
public boolean isAuthenticated() {
    return true;
}

@Override
public boolean isValid(final String arg0) {
    return true;
}

@Override
public boolean matches(final String id, final String aclExpr) {
    if (Strings.isNullOrEmpty(id) || Strings.isNullOrEmpty(aclExpr)) {
        return false;
    }
    return id.charAt(0) == aclExpr.charAt(0);
}

}

ACLProvider

public class ACLProvider implements org.apache.curator.framework.api.ACLProvider {

@Override
public List<ACL> getAclForPath(final String path) {

    final String firstLetter = String.valueOf(path.charAt(1));
    final Id FIRST_USER_LETTER = new Id("digest", firstLetter);

    final ACL acl = new ACL(Perms.ALL, FIRST_USER_LETTER);
    return Collections.singletonList(acl);
}

@Override
public List<ACL> getDefaultAcl() {
    throw new NotImplementedException();
}

}

Stack

org.apache.zookeeper.KeeperException$InvalidACLException: KeeperErrorCode = InvalidACL for /f
at org.apache.zookeeper.KeeperException.create(KeeperException.java:121)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:783)
at org.apache.curator.framework.imps.CreateBuilderImpl$11.call(CreateBuilderImpl.java:696)
at org.apache.curator.framework.imps.CreateBuilderImpl$11.call(CreateBuilderImpl.java:679)
at org.apache.curator.RetryLoop.callWithRetry(RetryLoop.java:107)
at org.apache.curator.framework.imps.CreateBuilderImpl.pathInForeground(CreateBuilderImpl.java:676)
at org.apache.curator.framework.imps.CreateBuilderImpl.protectedPathInForeground(CreateBuilderImpl.java:453)
at org.apache.curator.framework.imps.CreateBuilderImpl.forPath(CreateBuilderImpl.java:443)
at org.apache.curator.framework.imps.CreateBuilderImpl.forPath(CreateBuilderImpl.java:44)
at com.equifax.product.fraud.common.zookeeper.support.ZooKeeperSupport.create(ZooKeeperSupport.java:35)
at com.equifax.product.fraud.common.zookeeper.authentication.BasicMockZookeeperSecurity.setup(BasicMockZookeeperSecurity.java:40)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:73)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:224)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68)
at org.junit.runners.ParentRunner.run(ParentRunner.java:292)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

What is wrong?


Solution

  • I have found the solution. Basically the problem is the way to create the user with password using digest schemee. According to zookeeper documentation, digest auth use an ID and password in base64 encode SHA1, apparently ACL doesn't make that automatically when you add the use to ACL so I had to do by myself.

    public class ZookeeperSecurityUtil {
    
    private static final String SHA1 = "SHA1";
    private static final String COLON = ":";
    private static final String DIGEST_SCHEME = "digest";
    
    public static List<AuthInfo> getCredentialsFromSystemProperties() {
        final List<AuthInfo> authInfo = new ArrayList<>();
    
        final String user = System.getProperty("zookeeper.security.user");
        final String password = System.getProperty("zookeeper.security.password");
    
        authInfo.add(new AuthInfo(DIGEST_SCHEME, new String(user + COLON + password).getBytes(Charsets.UTF_8)));
    
        return authInfo;
    }
    public static String generateDigest(final String idPassword) throws NoSuchAlgorithmException {
        final String parts[] = idPassword.split(COLON, 2);
        final byte digest[] = MessageDigest.getInstance(SHA1).digest(idPassword.getBytes());
        return parts[0] + COLON + base64Encode(digest);
    }
    private static String base64Encode(final byte byteDigest[]) {
    
        return new String(Base64.getEncoder().encode(byteDigest));
    }
    }
    

    and

    public class ACLProvider implements org.apache.curator.framework.api.ACLProvider {
    
    private static final String ZK_DIGEST_SCHEME = "digest";
    
    @Override
    public List<ACL> getAclForPath(final String path) {
    
        final String user = System.getProperty("zookeeper.security.user");
        final String password = System.getProperty("zookeeper.security.password");
    
        Id zkId = null;
        try {
            zkId = new Id(ZK_DIGEST_SCHEME, ZookeeperSecurityUtil.generateDigest(user + ":" + password));
        } catch(final NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    
        final ACL acl = new ACL(Perms.ALL, zkId);
        return Collections.singletonList(acl);
    }
    
    @Override
    public List<ACL> getDefaultAcl() {
        throw new NotImplementedException();
    }
    
    }