Goals
In this tutorial you will see how to connect Neo4j to a LDAP server for authentication and authorization (only available with the Enterprise Edition).
Setup an Openldap server
We need to have a LDAP server with some data. For this purpose, I will use the following docker image : https://github.com/osixia/docker-openldap
By default the domain is dc=example,dc=org
and the admin is cn=admin,dc=example,dc=org
with the password admin
.
We can pass an argument to docker to add a ldif folder for initiate the ldap server with some data at the startup.
This is the ldif file that I use :
dn: ou=users,dc=example,dc=org
objectClass: organizationalUnit
objectClass: top
ou: users
dn: ou=groups,dc=example,dc=org
objectClass: organizationalUnit
objectClass: top
ou: groups
dn: cn=reader,ou=groups,dc=example,dc=org
objectClass: groupOfNames
objectClass: top
cn: reader
member:uid=reader,ou=users,dc=example,dc=org
dn: cn=publisher,ou=groups,dc=example,dc=org
objectClass: groupOfNames
objectClass: top
cn: publisher
member:uid=publisher,ou=users,dc=example,dc=org
dn: cn=architect,ou=groups,dc=example,dc=org
objectClass: groupOfNames
objectClass: top
cn: architect
member:uid=architect,ou=users,dc=example,dc=org
dn: cn=admin,ou=groups,dc=example,dc=org
objectClass: groupOfNames
objectClass: top
cn: admin
member:uid=admin,ou=users,dc=example,dc=org
dn: uid=reader,ou=users,dc=example,dc=org
objectClass: organizationalPerson
objectClass: person
objectClass: extensibleObject
objectClass: uidObject
objectClass: inetOrgPerson
objectClass: top
cn: Reader User
givenName: Reader
sn: reader
uid: reader
mail: reader@example.com
ou: users
userpassword: test
memberOf: cn=reader,ou=groups,dc=example,dc=org
dn: uid=publisher,ou=users,dc=example,dc=org
objectClass: organizationalPerson
objectClass: person
objectClass: extensibleObject
objectClass: uidObject
objectClass: inetOrgPerson
objectClass: top
cn: Publisher User
givenName: Publisher
sn: publisher
uid: publisher
mail: publisher@example.com
ou: users
userpassword: test
memberOf: cn=publisher,ou=groups,dc=example,dc=org
dn: uid=architect,ou=users,dc=example,dc=org
objectClass: organizationalPerson
objectClass: person
objectClass: extensibleObject
objectClass: uidObject
objectClass: inetOrgPerson
objectClass: top
cn: Architect User
givenName: Architect
sn: architect
uid: architect
mail: architect@example.com
ou: users
userpassword: test
memberOf: cn=architect,ou=groups,dc=example,dc=org
dn: uid=admin,ou=users,dc=example,dc=org
objectClass: organizationalPerson
objectClass: person
objectClass: extensibleObject
objectClass: uidObject
objectClass: inetOrgPerson
objectClass: top
cn: Admin User
givenName: Architect
sn: admin
uid: admin
mail: admin@example.com
ou: users
userpassword: test
memberOf: cn=admin,ou=groups,dc=example,dc=org
As you can see, it only defines :
-
an organization unit for groups :
ou=groups,dc=example,dc=org
, with the following groups-
group admin
cn=admin,ou=groupss,dc=example,dc=org
with theadmin
-
group architect
cn=architect,ou=groups,dc=example,dc=org
with thearchitect
-
group publisher
cn=publisher,ou=groups,dc=example,dc=org
with thepublisher
-
group reader
cn=reader,ou=groups,dc=example,dc=org
with thereader
-
-
an organization unit for users :
ou=users,dc=example,dc=org
, with the following users-
user admin
uid=admin,ou=users,dc=example,dc=org
-
user architect
uid=architect,ou=users,dc=example,dc=org
-
user publisher
uid=publisher,ou=users,dc=example,dc=org
-
user reader
uid=reader,ou=users,dc=example,dc=org
-
INFO: users are must be linked to groups via the memberOf
attribut
In my case, I have stored this ldif file in this folder /home/bsimard/ldif
, so the command to start the docker image is the following :
docker run \
--volume /home/bsimard/ldif/:/container/service/slapd/assets/config/bootstrap/ldif/custom \
osixia/openldap:1.1.10 \
--copy-service \
--loglevel debug
Neo4j configuration
Now we have to configure Neo4j to use the LDAP.
For this, edit the NEO4J_HOME/conf/neo4j.conf
and put those properties :
#********************************************************************
# Security Configuration
#********************************************************************
# The authentication and authorization provider that contains both users and roles.
# This can be one of the built-in `native` or `ldap` auth providers,
# or it can be an externally provided plugin, with a custom name prefixed by `plugin`,
# i.e. `plugin-<AUTH_PROVIDER_NAME>`.
dbms.security.auth_provider=ldap
# The time to live (TTL) for cached authentication and authorization info when using
# external auth providers (LDAP or plugin). Setting the TTL to 0 will
# disable auth caching.
#dbms.security.auth_cache_ttl=10m
# The maximum capacity for authentication and authorization caches (respectively).
#dbms.security.auth_cache_max_capacity=10000
# Set to log successful authentication events to the security log.
# If this is set to `false` only failed authentication events will be logged, which
# could be useful if you find that the successful events spam the logs too much,
# and you do not require full auditing capability.
#dbms.security.log_successful_authentication=true
#================================================
# LDAP Auth Provider Configuration
#================================================
# URL of LDAP server to use for authentication and authorization.
# The format of the setting is `<protocol>://<hostname>:<port>`, where hostname is the only required field.
# The supported values for protocol are `ldap` (default) and `ldaps`.
# The default port for `ldap` is 389 and for `ldaps` 636.
# For example: `ldaps://ldap.example.com:10389`.
#
# NOTE: You may want to consider using STARTTLS (`dbms.security.ldap.use_starttls`) instead of LDAPS
# for secure connections, in which case the correct protocol is `ldap`.
dbms.security.ldap.host=172.17.0.2
# Use secure communication with the LDAP server using opportunistic TLS.
# First an initial insecure connection will be made with the LDAP server, and then a STARTTLS command
# will be issued to negotiate an upgrade of the connection to TLS before initiating authentication.
#dbms.security.ldap.use_starttls=false
# The LDAP referral behavior when creating a connection. This is one of `follow`, `ignore` or `throw`.
# `follow` automatically follows any referrals
# `ignore` ignores any referrals
# `throw` throws an exception, which will lead to authentication failure
#dbms.security.ldap.referral=follow
# The timeout for establishing an LDAP connection. If a connection with the LDAP server cannot be
# established within the given time the attempt is aborted.
# A value of 0 means to use the network protocol's (i.e., TCP's) timeout value.
#dbms.security.ldap.connection_timeout=30s
# The timeout for an LDAP read request (i.e. search). If the LDAP server does not respond within
# the given time the request will be aborted. A value of 0 means wait for a response indefinitely.
#dbms.security.ldap.read_timeout=30s
#----------------------------------
# LDAP Authentication Configuration
#----------------------------------
# LDAP authentication mechanism. This is one of `simple` or a SASL mechanism supported by JNDI,
# for example `DIGEST-MD5`. `simple` is basic username
# and password authentication and SASL is used for more advanced mechanisms. See RFC 2251 LDAPv3
# documentation for more details.
dbms.security.ldap.authentication.mechanism=simple
# LDAP user DN template. An LDAP object is referenced by its distinguished name (DN), and a user DN is
# an LDAP fully-qualified unique user identifier. This setting is used to generate an LDAP DN that
# conforms with the LDAP directory's schema from the user principal that is submitted with the
# authentication token when logging in.
# The special token {0} is a placeholder where the user principal will be substituted into the DN string.
dbms.security.ldap.authentication.user_dn_template=uid={0},ou=users,dc=example,dc=org
# Determines if the result of authentication via the LDAP server should be cached or not.
# Caching is used to limit the number of LDAP requests that have to be made over the network
# for users that have already been authenticated successfully. A user can be authenticated against
# an existing cache entry (instead of via an LDAP server) as long as it is alive
# (see `dbms.security.auth_cache_ttl`).
# An important consequence of setting this to `true` is that
# Neo4j then needs to cache a hashed version of the credentials in order to perform credentials
# matching. This hashing is done using a cryptographic hash function together with a random salt.
# Preferably a conscious decision should be made if this method is considered acceptable by
# the security standards of the organization in which this Neo4j instance is deployed.
dbms.security.ldap.authentication.cache_enabled=false
#----------------------------------
# LDAP Authorization Configuration
#----------------------------------
# Authorization is performed by searching the directory for the groups that
# the user is a member of, and then map those groups to Neo4j roles.
# Perform LDAP search for authorization info using a system account instead of the user's own account.
#
# If this is set to `false` (default), the search for group membership will be performed
# directly after authentication using the LDAP context bound with the user's own account.
# The mapped roles will be cached for the duration of `dbms.security.auth_cache_ttl`,
# and then expire, requiring re-authentication. To avoid frequently having to re-authenticate
# sessions you may want to set a relatively long auth cache expiration time together with this option.
# NOTE: This option will only work if the users are permitted to search for their
# own group membership attributes in the directory.
#
# If this is set to `true`, the search will be performed using a special system account user
# with read access to all the users in the directory.
# You need to specify the username and password using the settings
# `dbms.security.ldap.authorization.system_username` and
# `dbms.security.ldap.authorization.system_password` with this option.
# Note that this account only needs read access to the relevant parts of the LDAP directory
# and does not need to have access rights to Neo4j, or any other systems.
dbms.security.ldap.authorization.use_system_account=true
# An LDAP system account username to use for authorization searches when
# `dbms.security.ldap.authorization.use_system_account` is `true`.
# Note that the `dbms.security.ldap.authentication.user_dn_template` will not be applied to this username,
# so you may have to specify a full DN.
dbms.security.ldap.authorization.system_username=cn=admin,dc=example,dc=org
# An LDAP system account password to use for authorization searches when
# `dbms.security.ldap.authorization.use_system_account` is `true`.
dbms.security.ldap.authorization.system_password=admin
# The name of the base object or named context to search for user objects when LDAP authorization is enabled.
# A common case is that this matches the last part of `dbms.security.ldap.authentication.user_dn_template`.
dbms.security.ldap.authorization.user_search_base=ou=users,dc=example,dc=org
# The LDAP search filter to search for a user principal when LDAP authorization is
# enabled. The filter should contain the placeholder token {0} which will be substituted for the
# user principal.
dbms.security.ldap.authorization.user_search_filter=(&(objectClass=*)(uid={0}))
# A list of attribute names on a user object that contains groups to be used for mapping to roles
# when LDAP authorization is enabled.
dbms.security.ldap.authorization.group_membership_attributes=memberOf
# An authorization mapping from LDAP group names to Neo4j role names.
# The map should be formatted as a semicolon separated list of key-value pairs, where the
# key is the LDAP group name and the value is a comma separated list of corresponding role names.
# For example: group1=role1;group2=role2;group3=role3,role4,role5
#
# You could also use whitespaces and quotes around group names to make this mapping more readable,
# for example: dbms.security.ldap.authorization.group_to_role_mapping=\
# "cn=Neo4j Read Only,cn=users,dc=example,dc=com" = reader; \
# "cn=Neo4j Read-Write,cn=users,dc=example,dc=com" = publisher; \
# "cn=Neo4j Schema Manager,cn=users,dc=example,dc=com" = architect; \
# "cn=Neo4j Administrator,cn=users,dc=example,dc=com" = admin
dbms.security.ldap.authorization.group_to_role_mapping=\
"cn=reader,ou=groups,dc=example,dc=org" = reader; \
"cn=publisher,ou=groups,dc=example,dc=org" = publisher; \
"cn=architect,ou=groups,dc=example,dc=org" = architect; \
"cn=admin,ou=groups,dc=example,dc=org" = admin
Now you can restart Neo4j, and open the Neo4j browser (http://localhost:7474) to test the connection.
How it works ?
There is two steps : authentication and authorization.
authentication
The authentication is made by a direct bind of the user with the DN defined like this : Neo4j replace the user login (ie .{0}
) in the configuration key dbms.security.ldap.authentication.user_dn_template.
In the example, if you try to connect with the user admin
, Neo4j will do a bind with uid=admin,ou=Users,dc=example,dc=org
Authorization
Now Neo4j knows that the user is valid, so it looks up for the user’s groups.
For this, it needs a system account to searh the authorizations. To do it, it performs :
* the ldap query configure by dbms.security.ldap.authorization.user_search_filter by replacing the {0}
with the user’s login : (&(objectClass=*)(uid={0}))
* inside the DN configure by dbms.security.ldap.authorization.user_search_base : ou=users,dc=example,dc=org
Once it has found the correspondig user, it will take the attribute configure by dbms.security.ldap.authorization.group_membership_attributes (so memberOf
in our example) to have the list of all the user’s group.
Finally, it can makes the role mapping as defined in dbms.security.ldap.authorization.group_to_role_mapping.
Good to know
-
The LDAP connector is only available into the Enterprise Edition
-
Configuration is simple
-
LDAP groups must be linked into users DN (via an attribut like memberOf in our case)
-
In this example we have used a system account but it’s not necessary if users have the right to
BIND
&SEARCH
themself