From 4166e0133649c3c66c37af36bc79efb836a238d1 Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Tue, 25 Sep 2018 13:47:34 +1200 Subject: [PATCH] Add DNS SRV support for LDAP server discovery. LDAP servers can be advertised on a network by registering DNS SRV records for _ldap._tcp.. The OpenLDAP command-line tools know how to find servers via those records, if no server name is provided by the user. Teach PostgreSQL to follow the same convention, using non-standard extensions provided by OpenLDAP, where available. Author: Thomas Munro Reviewed-by: Discussion: --- doc/src/sgml/client-auth.sgml | 18 +++++++++++ src/backend/libpq/auth.c | 58 +++++++++++++++++++++++++++++++++-- src/backend/libpq/hba.c | 2 ++ 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index c2114021c3..2116e636be 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1671,6 +1671,15 @@ ldap[s]://host[:port]/ldapsearchattribute=uid. + + If PostgreSQL was compiled with OpenLDAP as + the LDAP client library, the ldapserver setting may be + omitted. In that case, the hostname and port are looked up via DNS + service records. The "SRV" records for the service + _ldap._tcp.domain are requested, where + domain is extracted from basedn. + + Here is an example for a simple-bind LDAP configuration: @@ -1716,6 +1725,15 @@ host ... ldap ldapserver=ldap.example.net ldapbasedn="dc=example, dc=net" ldapse + + Here is an example for a search+bind configuration that uses DNS SRV + discovery to find the hostname and port for the LDAP service using the + domain name example.net": + +host ... ldap ldapurl="ldap:///ou=people,dc=example,dc=net?cn" + + + Since LDAP often uses commas and spaces to separate the different diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 8517565535..203f08adac 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -2354,8 +2354,51 @@ InitializeLDAPConnection(Port *port, LDAP **ldap) { char *uri; - uri = psprintf("%s://%s:%d", scheme, port->hba->ldapserver, - port->hba->ldapport); + /* + * If the user provided no hostname, we can ask OpenLDAP to try to + * find one by extracting a domain name from the base DN and then + * using a DSN SRV record for _ldap._tcp.. If one or more + * such SRV records have been defined, we can get a hostname and + * port. + */ + if (!port->hba->ldapserver || port->hba->ldapserver[0] == '\0') + { + char *domain; + char *hostlist; + char *end; + + /* ou=blah,dc=foo,dc=bar -> foo.bar */ + if (ldap_dn2domain(port->hba->ldapbasedn, &domain)) + { + ereport(LOG, + (errmsg("could not extract domain name from basedn"))); + return STATUS_ERROR; + } + /* Look up host:port using DNS SRV for _ldap._tcp.foo.bar */ + if (ldap_domain2hostlist(domain, &hostlist)) + { + ereport(LOG, + (errmsg("could not look up a hostlist for %s", + domain))); + ldap_memfree(domain); + return STATUS_ERROR; + } + ldap_memfree(domain); + /* + * OpenLDAP already ordered by weight and shuffled equal weight + * servers, so we'll just take the first one. The string is + * of the format "host:port", separated by spaces. + */ + if ((end = strchr(hostlist, ' '))) + *end = '\0'; + uri = psprintf("%s://%s", scheme, hostlist); + ldap_memfree(hostlist); + } + else + { + uri = psprintf("%s://%s:%d", scheme, port->hba->ldapserver, + port->hba->ldapport); + } r = ldap_initialize(ldap, uri); pfree(uri); if (r != LDAP_SUCCESS) @@ -2504,12 +2547,23 @@ CheckLDAPAuth(Port *port) int r; char *fulluser; +#ifdef HAVE_LDAP_INITIALIZE + /* OpenLDAP allows empty hostname, if we have a basedn. */ + if ((!port->hba->ldapserver || port->hba->ldapserver[0] == '\0') && + (!port->hba->ldapbasedn || port->hba->ldapbasedn[0] == '\0')) + { + ereport(LOG, + (errmsg("LDAP server not specified, and no ldapbasedn"))); + return STATUS_ERROR; + } +#else if (!port->hba->ldapserver || port->hba->ldapserver[0] == '\0') { ereport(LOG, (errmsg("LDAP server not specified"))); return STATUS_ERROR; } +#endif if (port->hba->ldapport == 0) { diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 1a65ec87bd..8cd09a49b5 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1500,7 +1500,9 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) */ if (parsedline->auth_method == uaLDAP) { +#ifndef HAVE_LDAP_INITIALIZE MANDATORY_AUTH_ARG(parsedline->ldapserver, "ldapserver", "ldap"); +#endif /* * LDAP can operate in two modes: either with a direct bind, using -- 2.17.0