I remember this code!

Sure, let's get it out there, *as long as* someone is going to maintain it going forward.

I had been hoping to see more rallying behind how to use the JWT integration for OAuth and SAML workflows, but no one's done any walkthroughs / blogposts that I've seen. Putting those things in front of LDAP may be easier (but would be outside the scope of CouchDB...)

While we're on the topic of donation, any chance of weatherreport getting donated? You had brought this up a few months ago, Jay, and I'd love to see that in 3.2 as well.

-Joan



On 2020-09-01 3:02 p.m., Jay Doane wrote:
Greetings,

In 2015 IBM Cloudant developed an LDAP based authentication handler for
its CouchDB 2.x-based Cloudant Local offering. Since then, it has been used
in production on several large Cloudant Local deployments, accruing many
bug fixes and enhancements in the process.

Over the years, there has clearly been interest in using LDAP with CouchDB
[1], so it seems like the ldap_auth functionality might be something the
greater community could benefit from. If there are no objections from the
community or PMC, I'd be happy to open a PR in the hopes of getting it
included in CouchDB 3.2. To give an idea of what it's all about, I've
included the README.md contents below.

Thanks,
Jay

[1] https://couchdb.markmail.org/search/?q=LDAP

README.md:

# Delegating Basic and Cookie authentication and authorization to LDAP

CouchDB includes a built-in security model, with self-contained
authentication and authorization which rely on .ini files and/or user
databases to store user data, including usernames (uids), password
hashes, and user roles for accessing database resources.

For an organization which already uses an LDAP service for access
control, it may make more sense to delegate authentication and
authorization services to LDAP when accessing CouchDB. This is where
`ldap_auth` comes in.

At a high level, when `ldap_auth` is configured, it completely
replaces the default Basic and Cookie authentication and
authorization. It ignores .ini files and user databases altogether,
and attempts to validate user credentials using the configured LDAP
service. Once credentials have been authentication by the LDAP
service, `ldap_auth` determines the user's roles (which in turn
determine what the user is authorized to access) based on the LDAP
groups of which the user is a member.

## ldap_interface

This low-level interface module uses
[eldap](http://www.erlang.org/doc/man/eldap.html) to connect to,
authenticate, and search LDAP server(s) which are configured to model
user accounts and their associated CouchDB roles.

### Configuration

All configuration is done via `.ini` file, mostly in the `[ldap_auth]`
section. The following parameters can be modified, and have the
associated defaults in parentheses:

- `servers (127.0.0.1)` one or more LDAP servers; defaults to a single
host, but could be a comma separated list (note that all servers must
use the same port, a limitation of the underlying eldap library)

- `port (389)` LDAP server port for un-encrypted communication

- `ssl_port (636)` LDAP server port for encrypted communication

- `use_ssl (true)` if `true`, use TLS to encrypt traffic to LDAP
servers

- `timeout (5000)` milliseconds to wait for a response from an LDAP
server before throwing an error

- `user_base_dn (ou=users,dc=example,dc=com)` defines a directory
location to start searching for users

- `user_classes (person)` defines which `objectClass`es indicate
a particular entry as a user during search

- `user_uid_attribute (uid)` defines which attribute maps to
username

- `group_base_dn (ou=groups,dc=example,dc=com)` defines directory
location to start searching for groups

- `group_classes (posixGroup)` defines which `objectClass`es indicate
a particular entry as a group during search

- `group_member_attribute (memberUid)` defines which group attribute
maps to user Uid

- `group_role_attribute (description)` defines which group attribute
maps to a particular role

- `searcher_dn (uid=ldapsearch,ou=users,dc=example,dc=com)` defines
the DN to use when searching for users and groups

- `searcher_password (secret)` defines the password for the
`searcher_dn` above

- `user_bind_dns ([])` defines one or more base DNs into which the
authenticating user's username can be inserted as the
`user_uid_attribute`, and used to bind directly. See the "Efficiency
Considerations" section below for details

**Please note** that at least one of the above parameters must be set
inside the `[ldap_auth]` section of a .ini configuration file in order
for ldap_auth to be considered "configured", and to function as an
authentication/authorization handler. Failure to explicitly set at
least one `[ldap_auth]` parameter will result in the system using the
default basic and cookie authentication/authorization handlers instead.

### Efficiency Considerations

   `ldap_auth` effectively has 2 modes of operation, depending on
whether `user_bind_dns` has been defined or not. Both modes return a
list of roles associated with a user upon authentication success.

   By default, `ldap_auth` opens a connection to an LDAP server and
binds the connection handle with the searcher DN and password. It then
uses that connection to search for a user record with the
authenticating username. If a matching user DN is found, it opens a
second connection to the server and attempts to bind that connection
using the username and password credentials. If the bind is
successful, it closes that connection, and uses the original
"searcher" bound connection to search for groups which contain the
`group_member_attribute` matching the user's username. Those groups
have a `group_role_attribute` which indicates the actual roles for the
user.

   On the other hand, if you know in advance that all your users' DNs
can be constructed by inserting their usernames into the following
pattern: `$uid_attribute=$username,$user_bind_dn`
(e.g. `uid=jay,ou=users,dc=example,dc=com`), then you can configure
one or more `user_bind_dns`, and `ldap_auth` will not make use of the
searcher DN, nor `user_base_dn`, but instead attempt to bind directly
with the constructed user DNs. If the bind is successful, that bound
connection is further used to make the same group search as described
in the above paragraph. Overall, this technique can be used to
eliminate several additional network round trips.

## ldap_auth

   Implements Basic and Cookie based authentication handlers, using
`ldap_interface:authorized_roles(Username, Password)` to obtain the
roles associated with a particular user.

   Using `ldap_auth` with basic authentication requires no client side
changes. A properly configured database will automatically attempt to
authenticate via LDAP using the supplied credentials.

   Similarly, for cookie authentication, POST credentials to the
`_session` endpoint to obtain an AuthSession cookie, which contains a
signed hash of its contents: name, time issued, and authorized
roles. Subsequent requests using that cookie will automatically use
the roles in the cookie until it expires. The following configuration
parameters in the `[couch_httpd_auth]` section are used:

- `timeout (600)` seconds until the AuthSession cookie expires

- `secret` the shared secret used to sign the AuthSession cookie,
it should be created when the cluster is provisioned

Reply via email to