Menu

LDAP Plugin

The LDAP plugin provides the ability for your RabbitMQ server to perform authentication (determining who can log in) and authorisation (determining what permissions they have) by deferring to an external LDAP server. To use this plugin, some editing of the RabbitMQ configuration file is required. You must enable the plugin, and then configure it. You are advised to read this entire page before starting.

The LDAP plugin is included in the RabbitMQ distribution. To enable it, use rabbitmq-plugins:

rabbitmq-plugins enable rabbitmq_auth_backend_ldap

You will then need to set the value of the auth_backends configuration item for the rabbit application to include rabbit_auth_backend_ldap. rabbit.auth_backends is a list of authentication/authoriation providers to try in order. For more information, see the Access Control guide.

The following example will configure RabbitMQ to only check LDAP for users, and ignore the internal database:

{rabbit,[{auth_backends, [rabbit_auth_backend_ldap]}]}

This will check LDAP first, and then fall back to the internal database if the user cannot be authenticated through LDAP:

{rabbit,[{auth_backends, [rabbit_auth_backend_ldap, rabbit_auth_backend_internal]}]}

The example below will check LDAP 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, but do not need a password there). If the user is not found in LDAP then a second attempt is made using only the internal database.

{rabbit,[{auth_backends, [{rabbit_auth_backend_ldap, rabbit_auth_backend_internal},
                                                     rabbit_auth_backend_internal]}]}

Basic configuration

You must then configure the plugin. This plugin has quite a few configuration options, but most have sensible defaults.

The most complex part of configuring the plugin pertains to authorisation (i.e. granting permissions to your users via LDAP). This is documented separately below and can be skipped if only using the plugin for authentication.

The default configuration allows all users to access all objects in all vhosts, but does not make them administrators. If you're happy with that, there is no need to read the documentation on authorisation.

The options not directly related to authorisation are:

servers
List of LDAP servers to attempt to bind to, in order. You almost certainly want to change this. Default: ["ldap"]
user_dn_pattern

There are two ways to convert a username as provided through AMQP to a Distinguished Name. The simplest way is via string substitution with user_dn_pattern. To do this, set user_dn_pattern to a string containing exactly one instance of ${username}.

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: "${username}"

dn_lookup_attribute, dn_lookup_base and dn_lookup_bind

The other way to convert a username to a Distinguished Name is via an LDAP lookup.

To do this, set dn_lookup_attribute to the name of the attribute that represents the user name, and 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.

If you want to do the lookup after binding, you can leave dn_lookup_bind set to its default of as_user. The LDAP plugin will then bind with the user's unadorned username to do the login, then look up its DN. In order for this to work your LDAP server needs to be configured to allow binding with the unadorned username (Microsoft Active Directory typically does this).

If you want to do the lookup before binding, you must set dn_lookup_bind to a tuple {UserDN, Password}. 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.

For example, if I set

{dn_lookup_attribute,   "userPrincipalName"},
{dn_lookup_base,        "DC=gopivotal,DC=com"}

I can authenticate as smacmullen@gopivotal.com and have my local Active Directory server return my real DN.

If you set both dn_lookup_attribute and user_dn_pattern then the approaches are combined: the plugin fills out the template and then searches for the DN.

Default: 'none', 'none' and 'as_user'

group_lookup_base

Base DN to search for nested groups. Used by the {in_group_nested, ...} query only. For more info see the section on queries.

In the below example ou=groups,dc=example,dc=com is the directory that contains all groups:

{group_lookup_base, "ou=groups,dc=example,dc=com"}

Default: 'none'

other_bind

For authentication this plugin binds to the LDAP server as the user it is trying to authenticate. This option controls how to bind for authorisation queries, and to retrieve the details of a user who is logging in without presenting a password (e.g. SASL EXTERNAL).

This option must either be one of the atoms as_user (to bind as the authenticated user) or anon (to bind anonymously), or a tuple {UserDN, Password} (to bind with a specified username and password).

Note that it is not possible to use the default as_user configuration when users connect without passwords. You must set other_bind to anon, or {UserDN, Password} for users to log in without passwords.

Default: as_user

use_ssl
Whether to use LDAP over SSL. Default: false
use_starttls
Whether to use LDAP secured with StartTLS. Requires Erlang R16B03 or later. Default: false

Only one of use_ssl and use_starttls can be true.

ssl_options
SSL client options to use with use_ssl or use_starttls. Uses the same SSL configuration as elsewhere in RabbitMQ. Requires Erlang R16A or later. Default: []
port
Port on which to connect to the LDAP servers. Default: 389.
timeout
LDAP connection timeout in milliseconds, or 'infinity' for no timeout. Default: infinity.
pool_size
LDAP queries worker pool size. Default: 64.
idle_timeout
Time in milliseconds after which inactive LDAP connections are closed. Set to 'infinity' for no timeout. Default: infinity.
log

Select true for verbose logging of the logic used by the LDAP plugin to make decisions. This is typically useful for debugging. Note; credentials in bind request outcomes will be scrubbed under this option.

Select network to additionally cause LDAP network traffic to be logged at a somewhat lower level, with bind request credentials scrubbed.

Select network_unsafe to cause LDAP network traffic to be logged at a lower level, with bind request credentials such as passwords, written to the logs; exercise caution.

Default: false

Configuring authorisation

Since LDAP has a view of the world which is rather different from that of RabbitMQ, we need to be able to configure the LDAP plugin to execute various queries against the LDAP database to determine whether the user is authorised to do various things. Authorisation is therefore controlled by three configuration options:

  • vhost_access_query
  • resource_access_query
  • tag_queries

Note that in order for a user to be able to access a virtual host, it must have been created within RabbitMQ; unlike users and permissions, virtual hosts cannot live entirely within LDAP.

Each defines a query that will determine whether a user has access to a vhost, whether they have access to a resource (e.g. exchange, queue, binding) and which tags they have.

The default values for these queries are {constant, true}, {constant, true} and [{administrator, {constant, false}}] respectively, granting all users access to all objects in all vhosts, but not making them administrators.

A query can be of one of several types, defined below. Since you are likely to write several queries, and since queries can be nested, it can be helpful to switch on the log configuration parameter documented above. This will cause the LDAP plugin to write fairly verbose descriptions of the queries it executes and the decisions it therefore makes to the RabbitMQ log.

All of the query types which take strings support string substitution, where variables pertaining to the query being made can be substituted in. Each of the three queries allow different substitutions:

vhost_access_query allows

  • ${username} - the user name provided at authentication
  • ${user_dn} - the distinguished name of the user
  • ${vhost} - the virtual host for which we are querying access

resource_access_query allows

  • ${username} - the user name provided at authentication
  • ${user_dn} - the distinguished name of the user
  • ${vhost} - the virtual host in which the resource resides
  • ${resource} - one of "exchange" or "queue" for the type of resource
  • ${name} - the name of the resource
  • ${permission} - one of "configure", "write" or "read" for the type of access being requested to the resource

The terms configure, write and read for resource access have the same meanings that they do for the built-in RabbitMQ permissions system, see http://www.rabbitmq.com/access-control.html

tag_queries allows

  • ${username} - the user name provided at authentication
  • ${user_dn} - the distinguished name of the user

Note that tag_queries consists of a proplist, mapping the name of a tag to a query to perform to determine whether or not the user has that tag. You must list queries for all tags that you want your users to be able to have.

Authorisation query reference

Constant Query

{constant, Bool}

This will always return either true or false, unconditionally granting or denying access.

Example:

{tag_queries, [{administrator, {constant, false}},
               {management,    {constant, true}}]}

This grants all users the ability to use the management plugin, but makes none of them administrators.

Exists Query

{exists, Pattern}

This will substitute variables into the pattern, and return true if there exists an object with the resulting DN.

Example:

{vhost_access_query, {exists, "ou=${vhost},ou=vhosts,dc=example,dc=com"}}

This grants access to all virtual hosts which exist as organisational units within ou=vhosts,dc=example,dc=com to all users.

In Group Query

{in_group, Pattern}
{in_group, Pattern, AttributeName}

Like the Exists Query, substitutes arguments into a pattern to look for an object. However, this query returns true if the logged in user is a member; checking either against the member attribute, or any named attribute.

Example:

{vhost_access_query, {in_group, "cn=${vhost}-users,ou=vhosts,dc=example,dc=com"}}

This grants access to virtual hosts when the user is listed as a member attribute of an appropriately named object (such as a groupOfNames) within ou=vhosts,dc=example,dc=com.

In Nested Group Query

{in_group_nested, Pattern}
{in_group_nested, Pattern, AttributeName}
{in_group_nested, Pattern, AttributeName, Scope}

Similar to the in_group query but also traverses group hierarchy, e.g. if the logged in user is a member of the group which is a member of another group. Membership is checked against the member attribute or any named attribute. Groups are searched in the DN defined by the group_lookup_base configuration key, or the dn_lookup_base variable if former is none. If both lookup base variables are set to none the query will always return false. Search scope can be set to either subtree or single_level.

  • subtree searches all objects contained under the lookup base
  • single_level searches for groups directly contained within the lookup base
Default value for scope is subrtee The query is using in-depth search up from user to target group. Search process will detect and skip cyclic paths. This query can be time and memory consuming if users are members of many groups, which are members of many groups as well. Use this query when groups for a membership hierarchy. It is still recommended to use plain {in_group, ...} query when possible: nested groups can be challenging to reason about.

Example:

[
            {group_lookup_base, "ou=groups,dc=example,dc=com"},
            {vhost_access_query, {in_group_nested, "cn=${vhost}-groups,ou=groups,dc=example,dc=com"}, "member", single_level}]

This grants access to virtual hosts when the user a member in group hierarchy defined by the member attribute values and located in the ou=groups,dc=example,dc=com directory.

For Query

{for, [{Name, Value, SubQuery}, ...]}

This allows you to split up a query and handle different cases with different subqueries.

Options should be a list of three-tuples, with each tuple containing a name, value and subquery. The name is the name of a variable (i.e. something that would go into a ${} substitution). The value is a possible value for that variable.

Note that the values are of different Erlang types; resource and permission have atom values (e.g. resource could be exchange) while the other keys have binary values (e.g. name might be <<"amq.fanout">>).

Example:

{resource_access_query,
 {for, [{resource, exchange, {for, [{permission, configure,
                                     {in_group, "cn=wheel,dc=example,dc=com"}
                                    },
                                    {permission, write, {constant, true}},
                                    {permission, read,  {constant, true}}
                                   ]}},
        {resource, queue,    {constant, true}}]}}

This allows members of the wheel group to declare and delete exchanges, and allow all users to do everything else.

Boolean Queries

{'not', SubQuery}
{'and', [SubQuery1, SubQuery2, SubQuery3, ...]}
{'or', [SubQuery1, SubQuery2, SubQuery3, ...]}

These can be used to combine subqueries with boolean logic. The 'and' and 'or' queries each take an arbitrarily long list of subqueries, returning true if all or any subqueries evaluate to true respectively.

Note that 'and', 'or' and 'not' are reserved words in Erlang, therefore the keywords need to be quoted with single quotes in the configuration file, as above.

Example:

{resource_access_query,
 {'or',
  [{'and',
    [{equals, "${name}", "test1"},
     {equals, "${username}", "user1"}]},
   {'and',
    [{equals, "${name}", "test2"},
     {'not', {equals, "${username}", "user1"}}]}
  ]}}

This example gives full access to objects called "test1" to "user1", and access to "test2" to everyone but "user1".

Equals Query

{equals, StringSubQuery1, StringSubQuery2}

Takes two strings, and checks that the one matches the other. Note that both strings are subqueries (of the string and attribute types below) in turn.

This can be useful in order to compare the value of one of the string substitution variables with a constant, or with an attribute value, etc.

Example:

{resource_access_query,
 {for, [{permission, configure, {equals, {attribute, "${user_dn}", "description"},
                                         {string, "can-declare-${resource}s"}
                                }
        },
        {permission, write, {constant, true}},
        {permission, read,  {constant, true}}
       ]
 }

This grants permissions to declare and delete exchanges and queues based on the presence of the strings "can-declare-exchanges" and "can-declare-queues" in the user's description field, and grants permission to write and read exchanges to everyone.

Match Query

{match, StringSubQuery, RESubQuery}

Takes a string and a regular expression, and checks that the one matches the other. Note that the string and the regular expression are both subqueries (of the string and attribute types below) in turn.

Example:

{resource_access_query, {match, {string, "${name}"},
                                {string, "^${username}-"}}
}

This allows users to configure, read and write any object whose name begins with their own username followed by a hyphen.

String Sub-query

{string, Pattern}

Just substitutes arguments into a string. As this returns a string rather than a boolean it should be used within a match or equals query. See above for example. As a shorthand you can use a plain string instead of {string, Pattern}.

Attribute Sub-query

{attribute, DNPattern, AttributeName}

Returns the value of an attribute of an object retrieved from LDAP. As this returns a string rather than a boolean it should be used within a match or equals query. See above for example.

Example configuration

Bringing it all together, here's a sample configuration. This makes all users able to access the management plugin, but makes none of them administrators. Access to virtual hosts is controlled by membership of a group per virtual host. Only members of admin can declare, delete or bind exchanges and queues, but all users can publish to exchanges and declare from queues.

[
  {rabbit, [{auth_backends, [rabbit_auth_backend_ldap]}]},
  {rabbitmq_auth_backend_ldap,
   [ {servers,               ["my-ldap-server"]},
     {user_dn_pattern,       "cn=${username},ou=People,dc=example,dc=com"},
     {use_ssl,               false},
     {port,                  389},
     {log,                   false},
     {vhost_access_query,    {in_group,
                              "ou=${vhost}-users,ou=vhosts,dc=example,dc=com"}},
     {resource_access_query,
      {for, [{permission, configure, {in_group, "cn=admin,dc=example,dc=com"}},
             {permission, write,
              {for, [{resource, queue,    {in_group, "cn=admin,dc=example,dc=com"}},
                     {resource, exchange, {constant, true}}]}},
             {permission, read,
              {for, [{resource, exchange, {in_group, "cn=admin,dc=example,dc=com"}},
                     {resource, queue,    {constant, true}}]}}
            ]
      }},
     {tag_queries,           [{administrator, {constant, false}},
                              {management,    {constant, true}}]}
   ]
  }
].