zookeeper source code analysis

ZooKeeper Security

This chapter analyzes the security-related content in the ZooKeeper project, focusing on the AuthenticationProvider interface.

AuthenticationProvider Analysis

This section provides a brief overview of the AuthenticationProvider interface. First, let's examine its definition:

public interface AuthenticationProvider {
  /**
   * Get the authentication scheme
   */
  String getScheme();

  /**
   * Handle authentication
   */
  KeeperException.Code handleAuthentication(ServerCnxn cnxn, byte authData[]);

  /**
   * Check if id matches aclExpr
   */
  boolean matches(String id, String aclExpr);

  /**
   * Check if authenticated
   */
  boolean isAuthenticated();

  /**
   * Validate id syntax
   */
  boolean isValid(String id);
}

The AuthenticationProvider interface provides five methods:

  1. getScheme: Retrieves the authentication scheme.
  2. handleAuthentication: Authenticates security data.
  3. matches: Checks if the id matches the ACL data information.
  4. isAuthenticated: Determines if authentication has passed.
  5. isValid: Validates the id syntax.

In the ZooKeeper project, there are multiple implementations of the AuthenticationProvider interface, as shown in the following image:

AuthenticationProvider

X509AuthenticationProvider Analysis

This section analyzes the X509AuthenticationProvider class, which uses the x509 security authentication strategy. Let's examine the implementation of the AuthenticationProvider interface methods, starting with the isValid method:

@Override
public boolean isValid(String id) {
  try {
    new X500Principal(id);
    return true;
  } catch (IllegalArgumentException e) {
    return false;
  }
}

This code attempts to create an X500Principal object with the id parameter. If an exception occurs, it indicates incorrect id syntax; otherwise, the syntax is correct.

Next, let's analyze the matches method:

@Override
public boolean matches(String id, String aclExpr) {
  if (System.getProperty(ZOOKEEPER_X509AUTHENTICATIONPROVIDER_SUPERUSER) != null) {
    return (id.equals(System.getProperty(ZOOKEEPER_X509AUTHENTICATIONPROVIDER_SUPERUSER))
            || id.equals(aclExpr));
  }

  return (id.equals(aclExpr));
}

The process flow is as follows:

  1. Check if the zookeeper.X509AuthenticationProvider.superUser system property exists.
  2. If it doesn't exist, compare id and aclExpr for string equality and return the result.
  3. If it exists: a. Compare id with the zookeeper.X509AuthenticationProvider.superUser property. b. Compare id with aclExpr. c. Return the result of the OR operation between the two comparisons.

Finally, let's analyze the handleAuthentication method:

@Override
public KeeperException.Code handleAuthentication(ServerCnxn cnxn,
                                                 byte[] authData) {
  X509Certificate[] certChain
    = (X509Certificate[]) cnxn.getClientCertificateChain();

  if (certChain == null || certChain.length == 0) {
    return KeeperException.Code.AUTHFAILED;
  }

  if (trustManager == null) {
    LOG.error("No trust manager available to authenticate session 0x{}",
              Long.toHexString(cnxn.getSessionId()));
    return KeeperException.Code.AUTHFAILED;
  }

  X509Certificate clientCert = certChain[0];

  try {
    trustManager.checkClientTrusted(certChain,
                                    clientCert.getPublicKey().getAlgorithm());
  } catch (CertificateException ce) {
    LOG.error("Failed to trust certificate for session 0x" +
              Long.toHexString(cnxn.getSessionId()), ce);
    return KeeperException.Code.AUTHFAILED;
  }

  String clientId = getClientId(clientCert);

  if (clientId.equals(System.getProperty(
    ZOOKEEPER_X509AUTHENTICATIONPROVIDER_SUPERUSER))) {
    cnxn.addAuthInfo(new Id("super", clientId));
    LOG.info("Authenticated Id '{}' as super user", clientId);
  }

  Id authInfo = new Id(getScheme(), clientId);
  cnxn.addAuthInfo(authInfo);

  LOG.info("Authenticated Id '{}' for Scheme '{}'",
           authInfo.getId(), authInfo.getScheme());
  return KeeperException.Code.OK;
}

The core process flow is as follows:

  1. Retrieve the certificate chain from the connection object.
  2. Perform security authentication using the trust manager.
  3. Get the client ID from the client certificate.
  4. Check if the client ID matches the superuser property and add the appropriate Id object to the connection.
  5. Create and add an Id object to the connection, then return authentication success.

Conclusion

This chapter analyzed the security authentication content in the ZooKeeper project, focusing on the AuthenticationProvider interface and its implementations. We examined the key methods in these classes and analyzed the ProviderRegistry class, which manages AuthenticationProvider interfaces, covering initialization and retrieval aspects.