This is a prerelease version.

View latest

LDAP Authentication

A Lightweight Directory Access Protocol (LDAP) server is a specialized server that stores and manages directory information in a hierarchical structure. It’s commonly used for centralized authentication and authorization, storing information like user credentials, groups, and permissions. Applications and systems query the LDAP server to retrieve or modify this information, often to authenticate users or manage access rights. It’s widely used in environments like enterprise networks for user management and directory services.

Hazelcast supports authentication and authorization against LDAP servers. Authentication verifies the provided name and password, and authorization maps roles to the authenticated user.

You can verify passwords during authentication by:

  • making a new LDAP bind operation with the given name and password

  • using a separate "admin connection" to verify the provided password against an LDAP object attribute.

There are several ways to retrieve role mappings:

  • attribute: The role name is stored as an attribute in the object representing the identity.

  • direct mapping: The identity object contains an attribute with reference to the role object(s).

  • reverse mapping: The role objects with a reference to the identity object are searched.

The direct and reverse mapping modes allow a role search recursion.

Table 1. LDAP Configuration Options

Option Name

Default Value

Description

url

URL of the LDAP server. The value is configured as the JNDI environment property, i.e. java.naming.provider.url.

socket-factory-class-name

Socket factory class name. The factory can be used for fine-grained configuration of the TLS protocol on top of the LDAP protocol, i.e. ldaps scheme.

parse-dn

false

If set to true, it treats the value of role-mapping-attribute as a DN and extracts only the role-name-attribute values as role names. If set to false, the whole value of role-mapping-attribute is used as a role name.

This option is only used when the role-mapping-mode option has the value attribute.

role-context

LDAP Context in which assigned roles are searched, e.g. ou=Roles,dc=hazelcast,dc=com.

This option is only used when the role-mapping-mode option has the value reverse.

role-filter

([role-mapping-attribute]={MEMBERDN})

LDAP search string which usually contains a placeholder {MEMBERDN} to be replaced by the provided login name, e.g. (member={MEMBERDN}).

If the role search recursion is enabled (see role-recursion-max-depth), the {MEMBERDN} is replaced by role DNs in the recurrent searches.

This option is only used when the role-mapping-mode option has the value reverse.

role-mapping-attribute

Name of the LDAP attribute which contains either the role name or role DN.

This option is used when the role-mapping-mode option has the value attribute or direct. If the mapping mode is reverse, the value is used in role-filter default value.

role-mapping-mode

attribute

Role mapping mode. It can have one of the following values:

  • attribute: The user object in the LDAP contains the role name in the given attribute. The role name can be parsed from a DN string when parse-dn=true No additional LDAP query is done to find assigned roles.

  • direct: The user object contains an attribute with DN(s) of assigned role(s). Role objects are loaded from the LDAP and the role name is retrieved from its attributes. Role search recursion can be enabled for this mode.

  • reverse: The role objects are located by executing an LDAP search query with the given role-filter. In this case, the role object usually contains attributes with DNs of the assigned users. Role search recursion can be enabled for this mode.

role-name-attribute

This option may refer to a name of LDAP attribute within the role object which contains the role name in case of direct and reverse role mapping mode. It may also refer to the attribute name within X.500 name stored in role-mapping-attribute when role-mapping-mode=attribute and parse-dn=true.

role-recursion-max-depth

1

Sets the maximum depth of role search recursion. The default value 1 means the role search recursion is disabled.

This option is only used when the role-mapping-mode option has a direct or reverse value.

role-search-scope

subtree

LDAP search scope used for role-filter search. It can have one of the following values:

  • subtree: Searches for objects in the given context and its subtree.

  • one-level: Searches just one level under the given context.

  • object: Searches (or tests) only for the context object itself (if it matches the filter criteria).

This option is only used when the role-mapping-mode option has the value reverse.

user-name-attribute

uid

LDAP attribute name whose value is used as a name in ClusterIdentityPrincipal added to the JAAS Subject.

system-user-dn

Admin account DN. If configured, then the following are true:

  • For the user and role object, search queries are used an admin connection instead of the "user" one created by LDAP bind with provided credentials.

  • LDAP authentication doesn’t expect the full user DN to be provided as a login name. It rather expects names like "jduke" than "uid=jduke,ou=Engineering,o=Hazelcast,dc=com";

  • The admin connection allows verifying the provided user credentials against a value defined in the password-attribute option.

system-user-password

Admin’s password (for system-user-dn account).

system-authentication

simple

Name of the authentication mechanism used for the admin LDAP connection. It’s used as a value for the JNDI environment property Context#SECURITY_AUTHENTICATION. You can specify GSSAPI to authenticate with the Kerberos protocol.

password-attribute

Credentials verification is done by the new LDAP binds by default. However, the password can be stored in a non-default LDAP attribute; in this case use password-attribute to configure which LDAP attribute (within the user object) contains the password. If the password-attribute option is provided, then the extra LDAP bind to verify credentials is not done and passwords are just compared within the Hazelcast code after retrieving the user object from LDAP server.

This option is only used when the admin connection is configured, i.e. when system-user-dn or system-authentication is defined.

user-context

LDAP context in which the user objects are searched, e.g., ou=Users,dc=hazelcast,dc=com.

This option is only used when the admin connection is configured, i.e. when system-user-dn or system-authentication is defined.

user-filter

(uid={login})

LDAP search string for retrieving the user objects based on the provided login name. It usually contains a placeholder substring {login} which is replaced by the provided login name.

This option is only used when the admin connection is configured, i.e. when system-user-dn or system-authentication is defined.

user-search-scope

subtree

LDAP search scope used for user-filter search. It can have one of the following values:

  • subtree: Searches for objects in the given context and its subtree.

  • one-level: Searches just one-level under the given context.

  • object: Searches (or tests) just for the context object itself (if it matches the filter criteria).

This option is only used when the admin connection is configured, i.e. when system-user-dn or system-authentication is defined.

skip-authentication

false

Flag which disables password verification and instead adds HazelcastPrincipal instances to the Subject.

This option is only used when the admin connection is configured, i.e. when system-user-dn or system-authentication is defined.

security-realm

If specified, the given realm name is used for authentication of a (temporary) Subject which is then used for doing LDAP queries.

This option is only used when the admin connection is configured, i.e. when system-user-dn or system-authentication is defined.

Detailed logging for LDAP authentication can be enabled by configuring a more verbose logger level for the com.hazelcast.security package as described in the Security Debugging section.

The LDAP authentication implementation provided by Hazelcast doesn’t handle LDAP referrals, i.e. references to other LDAP trees.

TLS protected LDAP server connections

The LDAP authentication type supports TLS protected connections to LDAP servers, using the ldaps protocol scheme. TLS is handled on the Java runtime side (JNDI API and URL handlers).

When using TLS, by default the LDAP provider uses the socket factory javax.net.ssl.SSLSocketFactory to create a TLS socket to communicate with the server, using the default JSSE configuration. By default, the server’s certificate is validated against Java default CA certificate store, and the hostname in the LDAP URL is verified against the name(s) in the server certificate. This behavior can be controlled globally by using javax.net.ssl.* properties, as the following example shows:

java -Djavax.net.ssl.trustStore=/opt/hazelcast.truststore \
  -Djavax.net.ssl.trustStorePassword=123456 \
  -Djavax.net.ssl.keyStore=/opt/hazelcast.keystore \
  -Djavax.net.ssl.keyStorePassword=123456 \
  ...

There can be also properties specific to vendor or Java version that enable more fine-grained control. Here is an example that disabls host name validation:

-Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true

When even more control is necessary, you can implement your own SSLSocketFactory and use its class name as the value in the ldap authentication option socket-factory-class-name.

Here is an example custom socket factory class:

package security.ldap;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyStore;
import java.security.SecureRandom;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

public class CustomSSLSocketFactory extends SSLSocketFactory {

    private static final SocketFactory INSTANCE = new CustomSSLSocketFactory();

    /**
     * JNDI uses this method when creating {@code ldaps} connections.
     */
    public static SocketFactory getDefault() {
        return INSTANCE;
    }

    private SSLSocketFactory delegate;

    public CustomSSLSocketFactory() {
        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            try (FileInputStream fis = new FileInputStream("/opt/ldap.truststore")) {
                trustStore.load(fis, "S3cr3t".toCharArray());
            }
            TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmFactory.init(trustStore);
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, tmFactory.getTrustManagers(), new SecureRandom());
            delegate = sc.getSocketFactory();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return delegate.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return delegate.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3) throws IOException {
        return delegate.createSocket(arg0, arg1, arg2, arg3);
    }

    @Override
    public Socket createSocket(String arg0, int arg1) throws IOException {
        return delegate.createSocket(arg0, arg1);
    }

    @Override
    public Socket createSocket(InetAddress arg0, int arg1) throws IOException {
        return delegate.createSocket(arg0, arg1);
    }

    @Override
    public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3) throws IOException {
        return delegate.createSocket(arg0, arg1, arg2, arg3);
    }

    @Override
    public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException {
        return delegate.createSocket(arg0, arg1, arg2, arg3);
    }
}

The following example shows a possible authentication configuration:

  • XML

  • YAML

            <realm name="ldapsRealm">
                <authentication>
                    <ldap>
                        <url>ldaps://ldapserver.acme.com</url>
                        <socket-factory-class-name>security.ldap.CustomSSLSocketFactory</socket-factory-class-name>
                        <role-mapping-attribute>cn</role-mapping-attribute>
                    </ldap>
                </authentication>
            </realm>
    realms:
      - name: ldapsRealm
        authentication:
          ldap:
             url: ldaps://ldapserver.acme.com
             socket-factory-class-name: security.ldap.CustomSSLSocketFactory
             role-mapping-attribute: cn

LDAP authentication is backed by the JNDI API in Java and also has failover support. You can configure multiple space-separated URLs in the <url> option:

  • XML

  • YAML

            <realm name="ldapFallbackRealm">
                <authentication>
                    <ldap>
                        <url>ldap://ldap-master.example.com ldap://ldap-backup.example.com</url>
                    </ldap>
                </authentication>
            </realm>
    realms:
      - name: ldapFallbackRealm
        authentication:
          ldap:
             url: ldap://ldap-master.example.com ldap://ldap-backup.example.com

LDAP can also be used for role retrieval when Kerberos authentication is used.