From e7194dc73ad65488233981652da57f87d3b9357a Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Thu, 13 Jul 2017 19:23:06 +1200 Subject: [PATCH] Allow custom search filters to be configured for LDAP auth. Before, only filters of the form "(=)" could be used to search an LDAP server. Allow filters to be built from prefix + user + suffix so that extra restrictions can be placed on the set of LDAP entries that can be used to log into PostgreSQL. --- doc/src/sgml/client-auth.sgml | 30 +++++++++++++++++++++ src/backend/libpq/auth.c | 11 +++++--- src/backend/libpq/hba.c | 61 ++++++++++++++++++++++++++++++++++++++++--- src/include/libpq/hba.h | 2 ++ 4 files changed, 97 insertions(+), 7 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 819db811b2..825c89ebe3 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1508,6 +1508,25 @@ omicron bryanh guest1 + ldapsearchprefix + + + The prefix of a search filter used to search for the user name + when doing search+bind authentication. This allows for more general + search filters than ldapsearchattribute. + + + + + ldapsearchsuffix + + + The suffix of a search filter used to search for the user name + when doing search+bind authentication. + + + + ldapurl @@ -1550,6 +1569,17 @@ ldap://host[:port]/ + When using search+bind mode, the search can be performed either with a + single attribute specified with ldapsearchattribute, + or with a custom search filter specified with + ldapsearchprefix and ldapsearchsuffix. + Specifying ldapsearchattribute=foo is equivalent + to specifying ldapsearchprefix="(foo=" ldapsearchsuffix=")". + If none of these options are specified explicitly, the default is + ldapsearchattribute=uid. + + + Here is an example for a simple-bind LDAP configuration: host ... ldap ldapserver=ldap.example.net ldapprefix="cn=" ldapsuffix=", dc=example, dc=net" diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index dd7de7c3a4..c3c9008c8c 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -2471,9 +2471,14 @@ CheckLDAPAuth(Port *port) attributes[0] = port->hba->ldapsearchattribute ? port->hba->ldapsearchattribute : "uid"; attributes[1] = NULL; - filter = psprintf("(%s=%s)", - attributes[0], - port->user_name); + /* Build an entirely custom filter or a single attribute filter? */ + if (port->hba->ldapsearchprefix != NULL) + filter = psprintf("%s%s%s", + port->hba->ldapsearchprefix, + port->user_name, + port->hba->ldapsearchsuffix); + else + filter = psprintf("(%s=%s)", attributes[0], port->user_name); r = ldap_search_s(ldap, port->hba->ldapbasedn, diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 42afead9fd..92844edb18 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1505,7 +1505,8 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) /* * LDAP can operate in two modes: either with a direct bind, using * ldapprefix and ldapsuffix, or using a search+bind, using - * ldapbasedn, ldapbinddn, ldapbindpasswd and ldapsearchattribute. + * ldapbasedn, ldapbinddn, ldapbindpasswd and either + * ldapsearchattribute or ldapsearchprefix and ldapsearchsuffix. * Disallow mixing these parameters. */ if (parsedline->ldapprefix || parsedline->ldapsuffix) @@ -1513,14 +1514,16 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) if (parsedline->ldapbasedn || parsedline->ldapbinddn || parsedline->ldapbindpasswd || - parsedline->ldapsearchattribute) + parsedline->ldapsearchattribute || + parsedline->ldapsearchprefix || + parsedline->ldapsearchsuffix) { ereport(elevel, (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, or ldapurl together with ldapprefix"), + errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchprefix, ldapsearchsuffix or ldapurl together with ldapprefix"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); - *err_msg = "cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, or ldapurl together with ldapprefix"; + *err_msg = "cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchprefix or ldapsearchsuffix or ldapurl together with ldapprefix"; return NULL; } } @@ -1534,6 +1537,36 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set"; return NULL; } + + /* + * When using search+bind, you can either user a simple attribute + * defaulting to "uid" or a fully custom search filter. You can't + * do both. + */ + if (parsedline->ldapsearchattribute && + (parsedline->ldapsearchprefix || parsedline->ldapsearchsuffix)) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("cannot use ldapsearchattribute together with ldapsearchprefix and ldapsearchsuffix"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "cannot use ldapsearchattribute together with ldapsearchprefix and ldapsearchsuffix"; + return NULL; + } + + /* If using a fully custom filter, you need to supply both ends. */ + if ((parsedline->ldapsearchprefix != NULL) != + (parsedline->ldapsearchsuffix != NULL)) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("ldapsearchprefix and ldapsearchsuffix must be specified together"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "ldapsearchprefix and ldapsearchsuffix must be specified together"; + return NULL; + } } if (parsedline->auth_method == uaRADIUS) @@ -1788,6 +1821,16 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap"); hbaline->ldapsearchattribute = pstrdup(val); } + else if (strcmp(name, "ldapsearchprefix") == 0) + { + REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchprefix", "ldap"); + hbaline->ldapsearchprefix = pstrdup(val); + } + else if (strcmp(name, "ldapsearchsuffix") == 0) + { + REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchsuffix", "ldap"); + hbaline->ldapsearchsuffix = pstrdup(val); + } else if (strcmp(name, "ldapbasedn") == 0) { REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap"); @@ -2266,6 +2309,16 @@ gethba_options(HbaLine *hba) CStringGetTextDatum(psprintf("ldapsearchattribute=%s", hba->ldapsearchattribute)); + if (hba->ldapsearchprefix) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapsearchprefix=%s", + hba->ldapsearchprefix)); + + if (hba->ldapsearchsuffix) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapsearchsuffix=%s", + hba->ldapsearchsuffix)); + if (hba->ldapscope) options[noptions++] = CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope)); diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 07d92d4f9f..c14322ffe6 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -80,6 +80,8 @@ typedef struct HbaLine char *ldapbinddn; char *ldapbindpasswd; char *ldapsearchattribute; + char *ldapsearchprefix; + char *ldapsearchsuffix; char *ldapbasedn; int ldapscope; char *ldapprefix; -- 2.13.2