LDAP Support
Overview
RabbitMQ can use LDAP to perform authentication and authorisation by deferring to external LDAP servers. This functionality is provided by a built-in plugin that has to be enabled.
Authentication and authorisation operations are translated into LDAP queries using templates configured by the RabbitMQ operator.
LDAP queries can be cached for a period of time for efficiency and reduced load on LDAP servers.
LDAP Operation Flow section provides a more detailed overview of how the plugin works.
The plugin primarily targets OpenLDAP and Microsoft Active Directory. Other LDAP v3 implementations should work reasonably well.
This guide provides a very brief overview of LDAP terms but generally assumes basic familiarity with LDAP. Several beginner-oriented LDAP primers are available elsewhere on the Web, for example, one, two, and the LDAP glossary.
This guide covers the LDAP operation flow used by RabbitMQ, how the LDAP model maps to the RabbitMQ permission model, how to use TLS to connect to LDAP servers, and what tools are available for troubleshooting and proxying of LDAP requests.
Prerequisites
RabbitMQ LDAP plugin depends on an LDAP client called eldap
. The library
ships with Erlang/OTP. On some operating systems, Erlang
is provided as a group of packages instead of one monolithic package, so
components such as eldap
must be installed separately from the main runtime.
On Debian and Ubuntu, eldap
is provided by the erlang-eldap
package:
sudo apt-get install -y erlang-eldap
LDAP support cannot be used on Erlang installations where the library is not available.
Please see the Erlang compatibility guide to learn more.
Enabling the Plugin
The LDAP plugin ships with RabbitMQ. To enable it, use rabbitmq-plugins:
rabbitmq-plugins enable rabbitmq_auth_backend_ldap
Enabling LDAP AuthN and AuthZ backends
After enabling the plugin it is necessary to configure the node to use it.
This involves
- Listing LDAP as an authentication (authN) and/or authorization (authZ) backend
- Configuring LDAP server endpoints
- Specifying what LDAP queries will be used for various authZ permission checks
The following example will configure RabbitMQ to only use LDAP for authentication and authorisation, and ignore the internal database:
# use LDAP exclusively for authentication and authorisation
auth_backends.1 = ldap
In advanced.config
file, the same settings would look like this:
{rabbit, [
{auth_backends, [rabbit_auth_backend_ldap]}
]}
The following example will instruct the node to try LDAP first and then fall back to the internal database if the user cannot be authenticated through LDAP:
# try LDAP first
auth_backends.1 = ldap
# fall back to the internal database
auth_backends.2 = internal
Same example in the advanced.config
format:
{rabbit,[
{auth_backends, [rabbit_auth_backend_ldap, rabbit_auth_backend_internal]}
]}
In the following example, LDAP will be used for authentication first. If the user is found in LDAP then the password will be checked against LDAP and subsequent authorisation checks will be performed against the internal database (therefore users in LDAP must exist in the internal database as well, optionally with a blank password). If the user is not found in LDAP then a second attempt is made using only the internal database.
# use LDAP for authentication first
auth_backends.1.authn = ldap
# use internal database for authorisation
auth_backends.1.authz = internal
# fall back to the internal database
auth_backends.2 = internal
In the advanced config format:
{rabbit,[{auth_backends, [{rabbit_auth_backend_ldap, rabbit_auth_backend_internal},
rabbit_auth_backend_internal]}]}
Configuration
Once the plugin is enabled and its backends are wired in, a number of LDAP-specific settings must be configured. They include a list of LDAP servers, authentication and authorisation settings, and more.
The default configuration allows all users to access all objects in all vhosts, but does not make them administrators. Restricting access is possible by configuring LDAP queries.
LDAP Servers
For the plugin to be able to connect to a LDAP server, at least one server hostname or IP address must be configured
using the auth_ldap.servers
key. If multiple values are provided,
List values can be hostnames or IP addresses. This value must be configured. The following
example configures the plugin to use two LDAP servers. They will be tried
in order until connection to one of them succeeds:
auth_ldap.servers.1 = ldap.eng.megacorp.local
auth_ldap.servers.2 = 192.168.0.100
The same examples using the classic config format:
[
{rabbitmq_auth_backend_ldap, [
{servers, ["ldap.eng.megacorp.local", "192.168.0.100"]}
]}
].
LDAP servers typically use port 389
and that's the port the
LDAP plugin will use by default. auth_ldap.port
can be used
to override this:
auth_ldap.servers.1 = ldap.eng.megacorp.local
auth_ldap.servers.2 = 192.168.0.100
auth_ldap.port = 6389
The same examples using the classic config format:
[
{rabbitmq_auth_backend_ldap, [
{servers, ["ldap.eng.megacorp.local", "192.168.0.100"]},
{port, 6389}
]}
].
TCP connections to LDAP servers can be given a timeout using the auth_ldap.timeout
configuration key:
auth_ldap.servers.1 = ldap.eng.megacorp.local
auth_ldap.servers.2 = 192.168.0.100
# 15 seconds in milliseconds
auth_ldap.timeout = 15000
The default is infinity
, or no timeout.
LDAP server connections are pooled to avoid excessive connection churn and LDAP server
load. By default the pool has up to 64 connections. This can be controlled using the
auth_ldap.connection_pool_size
setting:
auth_ldap.servers.1 = ldap.eng.megacorp.local
auth_ldap.servers.2 = 192.168.0.100
auth_ldap.connection_pool_size = 256
Pooled connections without activity are closed after a period of time
configurable via auth_ldap.idle_timeout
, in milliseconds
or infinity
:
auth_ldap.servers.1 = ldap.eng.megacorp.local
auth_ldap.servers.2 = 192.168.0.100
auth_ldap.connection_pool_size = 256
# 300 seconds in milliseconds
auth_ldap.idle_timeout = 300000
Values between 120 and 300 seconds are recommended.
Using TLS for LDAP Connections
Starting with Erlang 26, TLS client peer verification is enabled by default by the TLS implementation.
If client TLS certificate and key pair is not configured, TLS-enabled LDAP server connections will fail. If peer verification is not necessary, it can be disabled, otherwise a certificate and private key pair must be configured for LDAP connections.
It is possible to connect to LDAP servers using TLS. To instruct the
plugin to do so, set the auth_ldap.use_ssl
setting to true
.
If StartTLS is used by the LDAP server, use auth_ldap.use_starttls
instead.
Note that those settings are mutually exclusive (cannot be combined).
Both values default to false
.
Client side TLS settings are configured using ssl_options
, which
are very similar to TLS settings elsewhere in RabbitMQ.
Here is a minimalistic example:
auth_ldap.servers.1 = ldap.eng.megacorp.local
auth_ldap.servers.2 = 192.168.0.100
# enables TLS for connections to the LDAP server
auth_ldap.use_ssl = true
# Disables peer certificate chain verification. See the section on Peer Verification
# below.
#
# Doing so loses one of the key benefits of TLS and make the setup less secure
# but also simplifies node configuration.
auth_ldap.ssl_options.verify = verify_none
The plugin can also connect using StartTLS. This less older and secure option is not recommended but may be necessary with older LDAP servers:
auth_ldap.servers.1 = ldap.eng.megacorp.local
auth_ldap.servers.2 = 192.168.0.100
# Enables StartTLS for connections to the LDAP server.
# Prefer auth_ldap.use_ssl with reasonably modern LDAP servers!
auth_ldap.use_starttls = true
# Disables peer certificate chain verification. See the section on Peer Verification
# below.
#
# Doing so loses one of the key benefits of TLS and make the setup less secure
# but also simplifies node configuration.
auth_ldap.ssl_options.verify = verify_none
Client TLS Options Available for LDAP
There are multiple TLS client options available:
CA Certificate(s), Clint Certificate and Private Key
# local filesystem path to a CA certificate bundle file
auth_ldap.ssl_options.cacertfile = /path/to/ca_certificate.pem
# local filesystem path to a client certificate file
auth_ldap.ssl_options.certfile = /path/to/client_certfile.pem
# local filesystem path to a client private key file
auth_ldap.ssl_options.keyfile = /path/to/client_key.pem
SNI (Server Name Indication)
Server Name Indication (SNI) can be configured
for outgoing TLS connections to LDAP servers. When not set, the default will be the hostname used
for connection (see auth_ldap.servers.*
above).
# Sets Server Name Indication for LDAP connections.
# If an LDAP server host is available via multiple domain names, set this value
# to the preferred domain name target LDAP server
auth_ldap.ssl_options.sni = ldap.identity.eng.megacorp.local
Hostname Verification
Hostname verification should not be confused with peer certificate chain verification. These settings are orthogonal and can be combined.
# take wildcards into account when performing hostname verification
auth_ldap.ssl_options.hostname_verification = wildcard
# disables hostname verification
auth_ldap.ssl_options.hostname_verification = none
Peer Verification
Starting with Erlang 26, LDAP TLS clients will perform peer certificate chain verification by default.
Peer certificate chain verification should not be confused with hostname match verification. These settings are orthogonal and can be combined.
# Enables peer certificate chain verification.
# This behavior is the default starting with Erlang 26 (and thus RabbitMQ 3.13+)/
auth_ldap.ssl_options.verify = verify_peer
# Disables peer certificate chain verification.
#
# Doing so loses one of the key benefits of TLS and make the setup less secure
# but also simplifies node configuration.
auth_ldap.ssl_options.verify = verify_none
# if target LDAP server does not present a certificate, should the connection be aborted?
auth_ldap.ssl_options.fail_if_no_peer_cert = true
Peer Chain Verification Depth
Certificate chain verification depth can be increased for servers that use multiple intermediary certificates:
auth_ldap.ssl_options.depth = 5
TLS Versions Enabled
# use TLSv1.2 only
ssl_options.versions.1 = tlsv1.2
TLS Options in advanced.config
The below example uses an advanced.config
format:
[
{rabbitmq_auth_backend_ldap, [
{servers, ["ldap1.eng.megacorp.local", "ldap2.eng.megacorp.local"]},
{use_ssl, true},
{ssl_options, [{cacertfile, "/path/to/ca_certificate.pem"},
{certfile, "/path/to/server_certificate.pem"},
{keyfile, "/path/to/server_key.pem"},
{verify, verify_peer},
{fail_if_no_peer_cert, true}]},
{server_name_indication, "ldap.identity.eng.megacorp.local"},
{ssl_hostname_verification, wildcard}
]}
].
LDAP Query Caching for Efficiency and Reduced Load
A special cache backend can be used in combination with other backends to significantly reduce the load they generate on LDAP servers.
It is recommended that production clusters that rely on LDAP for authentication and authorization use it in combination with the caching backend. Caching intervals in the range of 15 to 60 seconds strike a good security and efficiency balance for most systems.
LDAP Essentials and Terminology
This section covers some basic LDAP terminology used in this document. For an LDAP primer, please refer to this overview by Digital Ocean and the LDAP glossary from ldap.com.
Term | Description |
---|---|
Bind | LDAP speak for "authentication request". |
Distinguished Name (DN) | A distinguished name is a unique key in an LDAP directory (tree) that identifies an object (like a user or a group). The plugin will translate a client-provided username into a distinguished name during the authentication stage (see below). One way to think of a DN is an absolute file path in a filesystem. |
Common Name (CN) | A short identifier of an object in the tree. This identifier will vary between object classes (types) in the LDAP database. For example, a person's common name will be the full name. A group's common name would be the name of that group. One way to think of a CN is a file name in a filesystem. |
Attribute | A property of an object (a key-value pair). Think of it as a field of an object in an object-oriented programming language. |
Object Class | A set of predefined attributes. Think of it as a type (class) in an object-oriented language. |
Entry | An LDAP database entity, for example, a person or a group. It has an object class associated with it and one or more attributes, including a common name. Since the entity is located somewhere in the LDAP database tree it also must have a distinguished name which uniquely identifies it. Entries is what LDAP plugin queries use (look up, check for membership, compare attributes of and so on). An LDAP database must have some entries (typically users, groups) in order to be practically useful for RabbitMQ authentication and authorisation. |
LDAP Operation Flow
In order to execute an LDAP query the plugin will open a connection to the first LDAP server on the list which is reachable. Then, depending on the credential configuration it will perform an anonymous bind or a "simple bind" (authenticate the user with the LDAP server using a username/password pair). The credentials used to perform the bind can be derived from the client-provided username as explained in the following section.
If vhost access query is configured it will be executed next, otherwise vhost access is granted unconditionally.
At this point the connection can be considered successfully negotiated and established. It should be possible to open a channel on it, for example. All further operations performed on the connection will execute one of the authorisation queries. For example, declaring a queue will execute a resource access query (covered below). Publishing a message to a topic exchange will additionally execute a topic access query. Please refer to the Access Control guide to learn more.
Usernames and Distinguished Names
During the simple bind phase, the user_dn_pattern
pattern is used to translate
the provided username into a value to be used for the bind. By default, the pattern
passes the provided value as-is (i.e. the pattern is ${username}
). If
user_bind_pattern
is specified, it takes precedence over
user_dn_pattern
. This can be handy if a different user_dn_pattern
needs to be used during the distinguished name lookup phase. Note that the above does
not apply to anonymous binds, nor when dn_lookup_bind
is not set to
as_user
.
Client connections provide usernames which are translated into Distinguished
Names (DNs) in LDAP.
There are two ways to do that. The simplest way is via string substitution
with user_dn_pattern
. To use this option, set
user_dn_pattern
to a string containing exactly one
instance of ${username}
, a variable that will be
substituted for the username value provided by the client.
For example, setting user_dn_pattern to
"cn=${username},ou=People,dc=example,dc=com"
would cause the username simon
to be converted to the
DN cn=simon,ou=People,dc=example,dc=com
. Default value is
"${username}"
, in other words, the username is used verbatim.
The other way to convert a username to a Distinguished
Name is via an LDAP lookup. To do this, set
auth_ldap.dn_lookup_attribute
to the name of the
attribute that represents the user name, and
auth_ldap.dn_lookup_base
to the base DN for the
query. The lookup can be done at one of two times, either
before attempting to bind as the user in question, or
afterwards.
To do the lookup after binding, leave
auth_ldap.dn_lookup_bind
set to its default
of as_user
. The LDAP plugin will then bind
with the user's plain (unmodified) username to do the login, then
look up its DN. In order for this to work the LDAP server
needs to be configured to allow binding with the plain
username (Microsoft Active Directory typically does this).
To do the lookup before binding, set dn_lookup_bind
, dn_lookup_base
and
dn_lookup_attribute
as follows. The LDAP plugin will then bind with these
credentials first to do the lookup, then bind with the user's DN and password
to do the login.
auth_ldap.dn_lookup_bind.user_dn = CN=myuser,OU=users,DC=gopivotal,DC=com
auth_ldap.dn_lookup_bind.password = test1234
auth_ldap.dn_lookup_attribute = userPrincipalName
auth_ldap.dn_lookup_base = DC=gopivotal,DC=com
Consider the following example:
auth_ldap.dn_lookup_attribute = userPrincipalName
auth_ldap.dn_lookup_base = DC=gopivotal,DC=com
With this configuration it is possible to authenticate using an email address
(userPrincipalName
values are typically email addresses)
and have the local Active Directory server return an actual DN to do
the login.
If both auth_ldap.dn_lookup_attribute
and auth_ldap.user_dn_pattern
are set then the approaches are
combined: the plugin fills out the template and then
searches for the DN.
auth_ldap.dn_lookup_bind
's default value is as_user
.
For auth_ldap.dn_lookup_base
and auth_ldap.dn_lookup_attribute
it is none
.