Skip to main content
Version: Mosquitto 2.9

LDAP Authentication

Premium

Introduction

The LDAP Authentication plugin can be used to authenticate MQTT clients through an LDAP server.

To use the plugin, a configuration file must be specified (see example configuration for an example of such a file and config file format section for the overview of all possible configuration parameters).

The plugin uses a separate account to connect (bind) to the LDAP server and checks username and password provided for each MQTT client authentication request against it.

Plugin Activation

To enable the LDAP Auth plugin on the broker, add the following to the mosquitto.conf file:

plugin /usr/lib/cedalo_ldap_auth.so

persistence_location /mosquitto/data

This is an example configuration snippet applicable to the docker container setup. For installations not running in a container the above configuration needs to be adjusted accordingly (namely the location of cedalo_ldap_auth.so dynamic library and persistence_location may differ).

persistence_location is used as a search base path for the plugin's config file.

In addition to modifying mosquitto.conf, ensure that you have the appropriate license to use the plugin.

Config File Format

The configuration is stored in a single JSON file (called ldap.json by default) located inside the persistence_location, which is defined in mosquitto.conf. To use a different config file name, specify the plugin_opt_config_file option with the custom file name under the LDAP plugin section in mosquitto.conf.

The file represents a configuration object consisting of a connection object, which defines a DN (Distinguished Name) and a password to bind to the LDAP server and perform user searches. It also includes additional options that specify how to locate the user within LDAP.

The fields of the configuration object are described below. To see the entire structure of the configuration file, take a look at the JSON schema at the bottom of this page. To see an example of the config file refer to the configuration example section.

The following fields of the config are mandatory:

  • connection (path: $.connection):
    Connection information to bind administrative user to the LDAP server in order to further perform search operations for authenticating users (type: object).
    • uri (path: $.connection.uri):
      URI of the LDAP server including protocol, port, and path (if any). For instance, ldap://ldap.example.com:389. (type: string).
    • credentials (path: $.connection.credentials):
      Credentials to use when binding administrative user to the LDAP server (type: object).
      • bindDn (path: $.connection.bindDn):
        Distinguished name to be used when binding administrative user to LDAP (type: string).
      • bindPassword (path: $.connection.credentials.bindPassword):
        Password to be used when binding administrative user to LDAP. If this field is a string then this string will be used as the password. However, if password is stored an environment variable or a file then this field must be an object with one of the properties below (type: string or object).
        • plain (path: $.connection.credentials.bindPassword.plain):
          Plaintext passoword for binding to LDAP (type: string).
        • env (path: $.connection.credentials.bindPassword.env):
          Name of the environment variable where the password for binding to LDAP is stored (type: string).
        • filePath (path: $.connection.credentials.bindPassword.filePath):
          Full path to the file where the password for binding to LDAP is stored (type: string).
  • options (path: $.options):
    Options for performing LDAP operations after binding (type: object).
    • userSearch (path: $.options.userSearch):
      Options related to searching for the user in LDAP directory tree. Whenever a client is authenticating to Mosquitto their username is used to search the LDAP directory tree and find a corresponding user DN (Distingusished Name) according to the options specified here. If the DN is found, the plugin then uses this DN and the provided password to bind the authenticating client to LDAP and authenticate it to the broker (type: object).
      • baseDn (path: $.options.baseDn):
        Base DN from which to perform the search for users (type: string).

Optional fields:

  • version (path: $.version):
    Version of the configuration file. Reserved for future use (type: string, defaults to: 1).
  • connection (path: $.connection):
    (type: object).
    • ssl (path: $.connection.ssl):
      SSL options for ldaps protocol. SSL must also be supported by the LDAP server. Note that the typical port for the LDAP secure connection is 636, so uri might also need to be changed when using SSL (type: object, defaults to: None).
      • ca (path: $.connection.ssl.ca):
        Plaintext custom certificate of the certificate authority that issued the LDAP server's certificate in PEM format, including the "begin" and "end" PEM headers. The newlines separating the PEM headers can be represented with \n. (type: string).
      • cert (path: $.connection.ssl.cert):
        Plaintext certificate of the client in PEM format (type: string).
      • key (path: $.connection.ssl.key):
        Plaintext private key of the client in PEM format (type: string).
      • caPath (path: $.connection.ssl.caPath):
        Same as ca but represents a path to the certificate file on the file system (type: string).
      • certPath (path: $.connection.ssl.certPath):
        Same as cert but represents a path to the certificate file on the file system (type: string).
      • keyPath (path: $.connection.ssl.keyPath):
        Same as key but represents a path to the private key file on the file system (type: string).
      • rejectUnauthorized (path: $.connection.ssl.rejectUnauthorized):
        Whether to reject connections to LDAP servers that provide TLS certificates not trusted by the client. In this case client is the broker (type: bool).
    • lazyConnect (path: $.connection.lazyConnect):
      Whether to skip checking for LDAP server availability during Mosquitto startup. If set to false, the plugin will fail if the LDAP server is not reachanble at startup. Note that this check is performed once on startup and helps to troubleshoot connectivity issues. With this option set to true, the plugin will not attempt to reach the LDAP until an authorization request is received from an MQTT client (type: bool, defaults to: false).
    • timeoutMs (path: $.connection.timeoutMs):
      Amount of time in milliseconds to wait before timing out when binding the administrative user to the LDAP server. 0 indicates no timeout. (type: number, defaults to: 0).
  • options (path: $.options):
    (type: object).
    • userSearch (path: $.options.userSearch):
      (type: object).
      • scope (path: $.options.userSearch.scope):
        Scope of the user search request. User search is performed on the LDAP server. base - search only the search base entry. one - search only the immediate children of the search base entry. sub - search the search base entry as well as all its subordinates to any depth. children - don't search the search baes entry but serach its subordinates to any depth. (type: string, one of: sub, base, children, one, defaults to: sub).
      • filter (path: $.options.userSearch.filter):
        The filter of the user search request as specified in RFC4515. To specify username of the MQTT client put %u into the filter string. For example: (cn=%u) where %u will be replaced by the username provided by the authenticating MQTT client (type: string, defaults to: (uid=%u)).
      • timeLimit (path: $.options.userSearch.timeLimit):
        The maximum number of seconds that user search may take. It sets the time limit on the LDAP server itself for processing the request. This may prevent straining the LDAP server. This is different from $.options.timeoutMs (described below), which sets the timeout on the client (broker). (type: number, defaults to: 10).
      • dereferenceAliases (path: $.options.userSearch.dereferenceAliases):
        LDAP alias dereferencing policy as specified in 4.5.1.3 of RFC4511. never - don't dereference aliases. always - dereference aliases both in searching and in locating the base object of the search. search - dereference aliases only when searching subordinates. fiind - dereference aliases only in locating the base object of the search. The always and find policies are applicable when aliases may be present in the base object ($.options.userSearch.baseDn). Conversly, if aliases are present in subordinates of the base object, use always or search. (type: string, one of: never, always, search, find, defaults to: never).
    • timeoutMs (path: $.options.timeoutMs):
      Amount of time in milliseconds to wait before timing out when performing operation such as search on the LDAP server. 0 indicates no timeout. (type: number, defaults to: 0).

You can also find all the available configuration options in the form of a JSON schema here

Configuration example

An example of the ldap.json is shown below:

{
"version": 1,
"connection": {
"uri": "ldap://ldap.example.com:389",
"ssl": {
"rejectUnauthorized": true
},
"credentials": {
"bindDn": "cn=admin,dc=example,dc=com",
"bindPassword": "password"
},
"timeoutMs": 10000,
"lazyConnect": false,
},
"options": {
"userSearch": {
"baseDn": "ou=Users,dc=example,dc=com",
"filter": "(uid=%u)",
"scope": "sub",
"derefAliases": "never",
"timeLimit": 10
},
"timeoutMs": 15000,
}
}

As per the example config above, during the broker startup the LDAP Authentication plugin will test reachability of the LDAP server at ldap://ldap.example.com:389 by performing a dummy TCP connection. If the server is unreachable, the plugin will fail and output an error. Otherwise, it will load normally and wait for authentication request from the MQTT cleints. If you don't want the plugin to be able to fail in case LDAP is unreachable on startup, then change the lazyConnect setting to true.

After receiving an authentication request form an MQTT client, the plugin will bind to the LDAP server using the bindDn and bindPassword options and a connect timeout of 10 seconds (connection.timeoutMs). It will use a username provided by the MQTT client and the settings defined under userSearch to perform a user search request on the LDAP server. It will traverse only subordinates of ou=Users,dc=example,dc=com base object to any depth (as specified by sub value) and apply (uid=%u) filter. It will not dereference any aliases (never) and terminate the LDAP query in case it takes more than 10 seconds (as defined in timeLimit). If for some reason the request hangs and response time exceeds 15 seconds (options.timeoutMs), the connection with the LDAP server will be forecefully terminated due to a timeout. In case of a timeout the authentication request of the client will be denied. On the other hand, if the user search request succeeds, the DN of the first result will be returned by the LDAP server and the broker will try binding to the server again using the found DN and the password provided by the MQTT client during the initial authentication request. In case this binding succeeds, user is authenticated to the broker.

Note that bindPassword entry may also be an object containing one of three possible entries: plain, env, or filePath. Below is an example with all three possible entries. However, in practice only a single one must be specified. Also note that plain entry is the same as just specifying password for bindPassword as a string.

{
...
"bindPassword": {
"plain": "<secret>",
"env": "<ENV_VAR_NAME>",
"filePath": "<file_path_to_client_id_if_no_env_given>"
}
...
}

Below you can see an example of SSL options with certificates and keys defined in plaintext using ca, cert, key options:

{
...
"ssl": {
"rejectUnauthorized": true,
"ca": "-----BEGIN CERTIFICATE-----\nMIIDqzCCApOgAwIBAgIUOcwjkkTUpeRgd+LiDodH...3VlVvDmCx67Scv4bsZnA==\n-----END CERTIFICATE-----",
"cert": "-----BEGIN CERTIFICATE-----\nMIIDTjCCAjYCFA+PcTt/ouPUq13W1gNxcZJ...9OM+OMFhw==\n-----END CERTIFICATE-----",
"key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhki...yWwtpUqG/7vOGOb2aA=\n-----END PRIVATE KEY-----"
},
...
}

You can alternatively use the file paths to certificate and key files stored on disk with caPath, certPath, and keyPath options.

Error handling

Any configuration or license errors will prevent the plugin from loading, and corresponding error messages will be logged.

If recoverable errors occur during the operation of the plugin, it will generate respective error messages prefixed with ERR: in the logs.

Notable behavior

The plugin enforces strict DN parsing as defined in LDAP standards (RFC 4514 and RFC 4512). DNs format must comply to these standards, for instance, relative DNs (RDNs) must be represented as key value pairs key=value. Multiple RDNs must be separated by commas and special characters in an RDN must be escaped with \.

Limitations

LDAP Authentication plugin currently supports authentication only. ACLs are not supported and should be specified using other means, such as an ACL file.

JSON Schema

Schema for all possible parameters for the ldap.json config file:

{
"type": "object",
"properties": {
"version": {
"description": "Version of the config file",
"type": "number",
"default": "1",
},
"connection": {
"type": "object",
"description": "Options needed to bind to LDAP server as an admin user in order to search for authenticating users",
"properties": {
"lazyConnect": {
"description": "Whether to try to check LDAP availablity on startup and fail if it is unreachable",
"type": "boolean",
"nullable": true,
"default": false
},
"uri": {
"description": "URI of the bind server including port, protocol, and path. For example: \"ldap://ldap.example.com:389\"",
"type": "string",
"nullable": false
},
"ssl": {
"description": "SSL configuration for the connection in case ldaps protocol is used",
"type": "object",
"nullable": true,
"properties": {
"ca": {
"type": "string",
"nullable": true,
"description": "Plaintext CA certificate to use for the connection",
},
"key": {
"type": "string",
"nullable": true,
"description": "Plaintext client's private key to use for the connection",
},
"cert": {
"type": "string",
"nullable": true,
"description": "Plaintext client's certificate to use for the connection",
},
"caPath": {
"type": "string",
"nullable": true,
"description": "Full path to the CA certificate file",
},
"keyPath": {
"type": "string",
"nullable": true,
"description": "Full path to the client's private key file",
},
"certPath": {
"type": "string",
"nullable": true,
"description": "Full path to the client's certificate file",
},
"rejectUnauthorized": {
"type": "boolean",
"nullable": true,
"description": "Whether to reject unauthorized server connections or not",
}
}
},
"credentials": {
"description": "Credentials to bind admin user to LDAP server",
"type": "object",
"properties": {
"bindDn": {
"description": "Distinguished name to use for binding to LDAP server",
"type": "string"
},
"bindPassword": {
"description": "Password to use for binding to LDAP server",
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"plain": {
"desciption": "Binding password as plaintext",
"type": "string",
"nullable": true
},
"env": {
"desciption": "Name of the environment variable which binding password is to be extracted from",
"type": "string",
"nullable": true
},
"filePath": {
"desciption": "Full path to the file on the filesystem which binding password is to be read from",
"type": "string",
"nullable": true
}
},
"required": [],
}
]
}
},
"nullable": false,
"required": ["bindDn", "bindPassword"]
},
"timeoutMs": {
"description": "Maximum number of milliseconds to wait for response when connecting to the LDAP server",
"type": "number",
"default": 0,
"nullable": true
},
},
"nullable": false,
"required": ["uri", "credentials"]
},
"options": {
"description": "Options for performing LDAP operations such as a search",
"type": "object",
"properties": {
"userSearch": {
"description": "Options related to searching for users in LDAP directory tree",
"type": "object",
"properties": {
"baseDn": {
"description": "The base of the subtree in which the search for the user is to be performed",
"type": "string"
},
"filter": {
"description": "Filter of the LDAP user search request. Must conform to syntax specified in RFC4515. %u gets substituted by MQTT client's username",
"type": "string",
"nullable": true,
"default": "(uid=%u)"
},
"scope": {
"description": "The scope of the search to be performed",
"type": "string",
"nullable": true,
"enum": ["base", "one", "sub", "children"],
"default": "sub"
},
"timeLimit": {
"description": "Amount of time in seconds that LDAP srever should spent processing the request. A value of 0 indicates no limit",
"type": "number",
"nullable": true,
"default": 10
},
"dereferenceAliases": {
"description": "The policy of dereferencing aliases as defined in section 4.5.1.3 of RFC4511",
"type": "string",
"nullable": true,
"enum": ["never", "always", "search", "find"],
"default": "never"
},
},
"nullable": false,
"required": ["baseDn"],
},
"timeoutMs": {
"description": "Maximum number of milliseconds to wait for server response to any LDAP operation after connecting",
"type": "number",
"default": 0,
"nullable": true
},
},
"nullable": false,
"required": ["userSearch"]
}
},
"nullable": false,
"required": ["version", "connection"],
}