Authentication Through LDAP

Overview

The LDAP connector allows email/password based authentication, backed by a LDAP directory.

The connector executes two primary queries:

  1. Finding the user based on the end user’s credentials.
  2. Searching for groups using the user entry.

Getting started

The dex repo contains a basic LDAP setup using OpenLDAP.

First start the LDAP server using docker-compose. This will run the OpenLDAP daemon in a Docker container, and seed it with an initial set of users.

  1. cd examples/ldap
  2. docker-compose up

This container is expected to print several warning messages which are normal. Once the server is up, run dex in another terminal.

  1. ./bin/dex serve examples/ldap/config-ldap.yaml

Then run the OAuth client in another terminal.

  1. ./bin/example-app

Go to http://localhost:5555, login and enter the username and password of the LDAP user: janedoe@example.com/foo. Add the “groups” scope as part of the initial redirect to add group information from the LDAP server.

Security considerations

Dex attempts to bind with the backing LDAP server using the end user’s plain text password. Though some LDAP implementations allow passing hashed passwords, dex doesn’t support hashing and instead strongly recommends that all administrators just use TLS. This can often be achieved by using port 636 instead of 389, and administrators that choose 389 are actively leaking passwords.

Dex currently allows insecure connections because the project is still verifying that dex works with the wide variety of LDAP implementations. However, dex may remove this transport option, and users who configure LDAP login using 389 are not covered by any compatibility guarantees with future releases.

Configuration

User entries are expected to have an email attribute (configurable through emailAttr), and a display name attribute (configurable through nameAttr). *Attr attributes could be set to “DN” in situations where it is needed but not available elsewhere, and if “DN” attribute does not exist in the record.

For the purposes of configuring this connector, “DN” is case-sensitive and should always be capitalised.

The following is an example config file that can be used by the LDAP connector to authenticate a user.

  1. connectors:
  2. - type: ldap
  3. # Required field for connector id.
  4. id: ldap
  5. # Required field for connector name.
  6. name: LDAP
  7. config:
  8. # Host and optional port of the LDAP server in the form "host:port".
  9. # If the port is not supplied, it will be guessed based on "insecureNoSSL",
  10. # and "startTLS" flags. 389 for insecure or StartTLS connections, 636
  11. # otherwise.
  12. host: ldap.example.com:636
  13. # Following field is required if the LDAP host is not using TLS (port 389).
  14. # Because this option inherently leaks passwords to anyone on the same network
  15. # as dex, THIS OPTION MAY BE REMOVED WITHOUT WARNING IN A FUTURE RELEASE.
  16. #
  17. # insecureNoSSL: true
  18. # If a custom certificate isn't provide, this option can be used to turn on
  19. # TLS certificate checks. As noted, it is insecure and shouldn't be used outside
  20. # of explorative phases.
  21. #
  22. # insecureSkipVerify: true
  23. # When connecting to the server, connect using the ldap:// protocol then issue
  24. # a StartTLS command. If unspecified, connections will use the ldaps:// protocol
  25. #
  26. # startTLS: true
  27. # Path to a trusted root certificate file. Default: use the host's root CA.
  28. rootCA: /etc/dex/ldap.ca
  29. # A raw certificate file can also be provided inline.
  30. # rootCAData: ( base64 encoded PEM file )
  31. # The DN and password for an application service account. The connector uses
  32. # these credentials to search for users and groups. Not required if the LDAP
  33. # server provides access for anonymous auth.
  34. # Please note that if the bind password contains a `$`, it has to be saved in an
  35. # environment variable which should be given as the value to `bindPW`.
  36. bindDN: uid=serviceaccount,cn=users,dc=example,dc=com
  37. bindPW: password
  38. # The attribute to display in the provided password prompt. If unset, will
  39. # display "Username"
  40. usernamePrompt: SSO Username
  41. # User search maps a username and password entered by a user to a LDAP entry.
  42. userSearch:
  43. # BaseDN to start the search from. It will translate to the query
  44. # "(&(objectClass=person)(uid=<username>))".
  45. baseDN: cn=users,dc=example,dc=com
  46. # Optional filter to apply when searching the directory.
  47. filter: "(objectClass=person)"
  48. # username attribute used for comparing user entries. This will be translated
  49. # and combined with the other filter as "(<attr>=<username>)".
  50. username: uid
  51. # The following three fields are direct mappings of attributes on the user entry.
  52. # String representation of the user.
  53. idAttr: uid
  54. # Required. Attribute to map to Email.
  55. emailAttr: mail
  56. # Maps to display name of users. No default value.
  57. nameAttr: name
  58. # Group search queries for groups given a user entry.
  59. groupSearch:
  60. # BaseDN to start the search from. It will translate to the query
  61. # "(&(objectClass=group)(member=<user uid>))".
  62. baseDN: cn=groups,dc=freeipa,dc=example,dc=com
  63. # Optional filter to apply when searching the directory.
  64. filter: "(objectClass=group)"
  65. # Following list contains field pairs that are used to match a user to a group. It adds an additional
  66. # requirement to the filter that an attribute in the group must match the user's
  67. # attribute value.
  68. userMatchers:
  69. - userAttr: uid
  70. groupAttr: member
  71. # Represents group name.
  72. nameAttr: name

The LDAP connector first initializes a connection to the LDAP directory using the bindDN and bindPW. It then tries to search for the given username and bind as that user to verify their password. Searches that return multiple entries are considered ambiguous and will return an error.

Example: Mapping a schema to a search config

Writing a search configuration often involves mapping an existing LDAP schema to the various options dex provides. To query an existing LDAP schema install the OpenLDAP tool ldapsearch. For rpm based distros run:

  1. sudo dnf install openldap-clients

For apt-get:

  1. sudo apt-get install ldap-utils

For smaller user directories it may be practical to dump the entire contents and search by hand.

  1. ldapsearch -x -h ldap.example.org -b 'dc=example,dc=org' | less

First, find a user entry. User entries declare users who can login to LDAP connector using username and password.

  1. dn: uid=jdoe,cn=users,cn=compat,dc=example,dc=org
  2. cn: Jane Doe
  3. objectClass: posixAccount
  4. objectClass: ipaOverrideTarget
  5. objectClass: top
  6. gidNumber: 200015
  7. gecos: Jane Doe
  8. uidNumber: 200015
  9. loginShell: /bin/bash
  10. homeDirectory: /home/jdoe
  11. mail: jane.doe@example.com
  12. uid: janedoe

Compose a user search which returns this user.

  1. userSearch:
  2. # The directory directly above the user entry.
  3. baseDN: cn=users,cn=compat,dc=example,dc=org
  4. filter: "(objectClass=posixAccount)"
  5. # Expect user to enter "janedoe" when logging in.
  6. username: uid
  7. # Use the full DN as an ID.
  8. idAttr: DN
  9. # When an email address is not available, use another value unique to the user, like uid.
  10. emailAttr: mail
  11. nameAttr: gecos

Second, find a group entry.

  1. dn: cn=developers,cn=groups,cn=compat,dc=example,dc=org
  2. memberUid: janedoe
  3. memberUid: johndoe
  4. gidNumber: 200115
  5. objectClass: posixGroup
  6. objectClass: ipaOverrideTarget
  7. objectClass: top
  8. cn: developers

Group searches must match a user attribute to a group attribute. In this example, the search returns users whose uid is found in the group’s list of memberUid attributes.

  1. groupSearch:
  2. # The directory directly above the group entry.
  3. baseDN: cn=groups,cn=compat,dc=example,dc=org
  4. filter: "(objectClass=posixGroup)"
  5. # The group search needs to match the "uid" attribute on
  6. # the user with the "memberUid" attribute on the group.
  7. userMatchers:
  8. - userAttr: uid
  9. groupAttr: memberUid
  10. # Unique name of the group.
  11. nameAttr: cn

To extract group specific information the DN can be used in the userAttr field.

  1. # Top level object example.coma in LDIF file.
  2. dn: dc=example,dc=com
  3. objectClass: top
  4. objectClass: dcObject
  5. objectClass: organization
  6. dc: example

The following is an example of a group query would match any entry with member=:

  1. groupSearch:
  2. # BaseDN to start the search from. It will translate to the query
  3. # "(&(objectClass=group)(member=<user DN>))".
  4. baseDN: cn=groups,cn=compat,dc=example,dc=com
  5. # Optional filter to apply when searching the directory.
  6. filter: "(objectClass=group)"
  7. userMatchers:
  8. - userAttr: DN # Use "DN" here not "uid"
  9. groupAttr: member
  10. nameAttr: name

There are cases when different types (objectClass) of groups use different attributes to keep a list of members. Below is an example of group query for such case:

  1. groupSearch:
  2. baseDN: cn=groups,cn=compat,dc=example,dc=com
  3. # Optional filter to search for different group types
  4. filter: "(|(objectClass=posixGroup)(objectClass=group))"
  5. # Use multiple user matchers so Dex will know which attribute names should be used to search for group members
  6. userMatchers:
  7. - userAttr: uid
  8. groupAttr: memberUid
  9. - userAttr: DN
  10. groupAttr: member
  11. nameAttr: name

Example: Searching a FreeIPA server with groups

The following configuration will allow the LDAP connector to search a FreeIPA directory using an LDAP filter.

  1. connectors:
  2. - type: ldap
  3. id: ldap
  4. name: LDAP
  5. config:
  6. # host and port of the LDAP server in form "host:port".
  7. host: freeipa.example.com:636
  8. # freeIPA server's CA
  9. rootCA: ca.crt
  10. userSearch:
  11. # Would translate to the query "(&(objectClass=posixAccount)(uid=<username>))".
  12. baseDN: cn=users,dc=freeipa,dc=example,dc=com
  13. filter: "(objectClass=posixAccount)"
  14. username: uid
  15. idAttr: uid
  16. # Required. Attribute to map to Email.
  17. emailAttr: mail
  18. # Entity attribute to map to display name of users.
  19. groupSearch:
  20. # Would translate to the query "(&(objectClass=group)(member=<user uid>))".
  21. baseDN: cn=groups,dc=freeipa,dc=example,dc=com
  22. filter: "(objectClass=group)"
  23. userMatchers:
  24. - userAttr: uid
  25. groupAttr: member
  26. nameAttr: name

If the search finds an entry, it will attempt to use the provided password to bind as that user entry.

Example: Searching a Active Directory server with groups

The following configuration will allow the LDAP connector to search a Active Directory using an LDAP filter.

  1. connectors:
  2. - type: ldap
  3. name: ActiveDirectory
  4. id: ad
  5. config:
  6. host: ad.example.com:636
  7. insecureNoSSL: false
  8. insecureSkipVerify: true
  9. bindDN: cn=Administrator,cn=users,dc=example,dc=com
  10. bindPW: admin0!
  11. usernamePrompt: Email Address
  12. userSearch:
  13. baseDN: cn=Users,dc=example,dc=com
  14. filter: "(objectClass=person)"
  15. username: userPrincipalName
  16. idAttr: DN
  17. emailAttr: userPrincipalName
  18. nameAttr: cn
  19. groupSearch:
  20. baseDN: cn=Users,dc=example,dc=com
  21. filter: "(objectClass=group)"
  22. userMatchers:
  23. - userAttr: DN
  24. groupAttr: member
  25. nameAttr: cn