From ec5bf08233a1f9cd9848895bda7f515203cde083 Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Fri, 3 Nov 2017 23:17:48 +1300 Subject: [PATCH] Allow ldaps when using ldap authentication. While ldaptls=1 provides a standards-based way to do LDAP authentication with StartTLS encryption, there was an earlier de facto standard way to do LDAP over SSL called LDAPS. Even though it's not enshrined in a standard, it's a common request since popular LDAP servers support it and some users prefer it. Author: Thomas Munro Discussion: https://postgr.es/m/CAEepm=1s+pA-LZUjQ-9GQz0Z4rX_eK=DFXAF1nBQ+ROPimuOYQ@mail.gmail.com --- doc/src/sgml/client-auth.sgml | 18 ++++++++++++++---- src/backend/libpq/auth.c | 21 ++++++++++++++++++--- src/backend/libpq/hba.c | 15 ++++++++++++++- src/include/libpq/hba.h | 1 + src/test/ldap/t/001_auth.pl | 41 +++++++++++++++++++++++++++++++++++++---- 5 files changed, 84 insertions(+), 12 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 99921ba079..612a857ea7 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1502,6 +1502,16 @@ omicron bryanh guest1 + + ldapscheme + + + Set to ldaps to use LDAPS. This is a non-standard + way of using LDAP with SSL supported by some popular LDAP server + implementation. + + + ldaptls @@ -1594,7 +1604,7 @@ omicron bryanh guest1 An RFC 4516 LDAP URL. This is an alternative way to write some of the other LDAP options in a more compact and standard form. The format is -ldap://host[:port]/basedn[?[attribute][?[scope][?[filter]]]] +ldap[s]://host[:port]/basedn[?[attribute][?[scope][?[filter]]]] scope must be one of base, one, sub, @@ -1615,9 +1625,9 @@ ldap://host[:port]/ To use encrypted LDAP connections, the ldaptls - option has to be used in addition to ldapurl. - The ldaps URL scheme (direct SSL connection) is not - supported. + option should be used in addition to ldapurl. A + non-standard alternative is to use + ldapschema=ldaps. diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index ab74fd8dfd..a2d32bc2ed 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -2320,11 +2320,20 @@ static int errdetail_for_ldap(LDAP *ldap); static int InitializeLDAPConnection(Port *port, LDAP **ldap) { + const char *scheme; + char *uri; int ldapversion = LDAP_VERSION3; int r; - *ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport); - if (!*ldap) + /* Use a minimal URI to initialize an LDAP connection. */ + scheme = port->hba->ldapscheme; + if (scheme == NULL) + scheme = "ldap"; + uri = psprintf("%s://%s:%d", scheme, port->hba->ldapserver, + port->hba->ldapport); + r = ldap_initialize(ldap, uri); + pfree(uri); + if (r != LDAP_SUCCESS) { #ifndef WIN32 ereport(LOG, @@ -2458,7 +2467,13 @@ CheckLDAPAuth(Port *port) } if (port->hba->ldapport == 0) - port->hba->ldapport = LDAP_PORT; + { + if (port->hba->ldapscheme != NULL && + strcmp(port->hba->ldapscheme, "ldaps") == 0) + port->hba->ldapport = LDAPS_PORT; + else + port->hba->ldapport = LDAP_PORT; + } sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0); diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index b2c487a8e8..61ff70f3a9 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1728,7 +1728,8 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, return false; } - if (strcmp(urldata->lud_scheme, "ldap") != 0) + if (strcmp(urldata->lud_scheme, "ldap") != 0 && + strcmp(urldata->lud_scheme, "ldaps") != 0) { ereport(elevel, (errcode(ERRCODE_CONFIG_FILE_ERROR), @@ -1739,6 +1740,7 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, return false; } + hbaline->ldapscheme = pstrdup(urldata->lud_scheme); hbaline->ldapserver = pstrdup(urldata->lud_host); hbaline->ldapport = urldata->lud_port; hbaline->ldapbasedn = pstrdup(urldata->lud_dn); @@ -1764,6 +1766,17 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, else hbaline->ldaptls = false; } + else if (strcmp(name, "ldapscheme") == 0) + { + REQUIRE_AUTH_OPTION(uaLDAP, "ldapscheme", "ldap"); + if (strcmp(val, "ldap") != 0 && strcmp(val, "ldaps") != 0) + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("invalid ldapscheme value: \"%s\"", val), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + hbaline->ldapscheme = pstrdup(val); + } else if (strcmp(name, "ldapserver") == 0) { REQUIRE_AUTH_OPTION(uaLDAP, "ldapserver", "ldap"); diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index e711bee8bf..5f68f4c666 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -75,6 +75,7 @@ typedef struct HbaLine char *pamservice; bool pam_use_hostname; bool ldaptls; + char *ldapscheme; char *ldapserver; int ldapport; char *ldapbinddn; diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl index 38760ece61..3b06ec1a6a 100644 --- a/src/test/ldap/t/001_auth.pl +++ b/src/test/ldap/t/001_auth.pl @@ -2,7 +2,7 @@ use strict; use warnings; use TestLib; use PostgresNode; -use Test::More tests => 15; +use Test::More tests => 17; my ($slapd, $ldap_bin_dir, $ldap_schema_dir); @@ -33,13 +33,16 @@ elsif ($^O eq 'freebsd') $ENV{PATH} = "$ldap_bin_dir:$ENV{PATH}" if $ldap_bin_dir; my $ldap_datadir = "${TestLib::tmp_check}/openldap-data"; +my $slapd_certs = "${TestLib::tmp_check}/slapd-certs"; my $slapd_conf = "${TestLib::tmp_check}/slapd.conf"; my $slapd_pidfile = "${TestLib::tmp_check}/slapd.pid"; my $slapd_logfile = "${TestLib::tmp_check}/slapd.log"; my $ldap_conf = "${TestLib::tmp_check}/ldap.conf"; my $ldap_server = 'localhost'; my $ldap_port = int(rand() * 16384) + 49152; +my $ldaps_port = $ldap_port + 1; my $ldap_url = "ldap://$ldap_server:$ldap_port"; +my $ldaps_url = "ldaps://$ldap_server:$ldaps_port"; my $ldap_basedn = 'dc=example,dc=net'; my $ldap_rootdn = 'cn=Manager,dc=example,dc=net'; my $ldap_rootpw = 'secret'; @@ -63,13 +66,27 @@ access to * database ldif directory $ldap_datadir +TLSCACertificateFile $slapd_certs/ca.crt +TLSCertificateFile $slapd_certs/server.crt +TLSCertificateKeyFile $slapd_certs/server.key + suffix "dc=example,dc=net" rootdn "$ldap_rootdn" rootpw $ldap_rootpw}); +# don't bother to check the server's cert (though perhaps we should) +append_to_file($ldap_conf, +qq{TLS_REQCERT never +}); + mkdir $ldap_datadir or die; +mkdir $slapd_certs or die; -system_or_bail $slapd, '-f', $slapd_conf, '-h', $ldap_url; +system_or_bail "openssl", "req", "-new", "-nodes", "-keyout", "$slapd_certs/ca.key", "-x509", "-out", "$slapd_certs/ca.crt", "-subj", "/cn=CA"; +system_or_bail "openssl", "req", "-new", "-nodes", "-keyout", "$slapd_certs/server.key", "-out", "$slapd_certs/server.csr", "-subj", "/cn=server"; +system_or_bail "openssl", "x509", "-req", "-in", "$slapd_certs/server.csr", "-CA", "$slapd_certs/ca.crt", "-CAkey", "$slapd_certs/ca.key", "-CAcreateserial", "-out", "$slapd_certs/server.crt"; + +system_or_bail $slapd, '-f', $slapd_conf, '-h', "$ldap_url $ldaps_url"; END { @@ -81,6 +98,7 @@ chmod 0600, $ldap_pwfile or die; $ENV{'LDAPURI'} = $ldap_url; $ENV{'LDAPBINDDN'} = $ldap_rootdn; +$ENV{'LDAPCONF'} = $ldap_conf; note "loading LDAP data"; @@ -178,9 +196,24 @@ test_access($node, 'test1', 0, 'combined LDAP URL and search filter'); note "diagnostic message"; +# note bad ldapprefix with a question mark that triggers a diagnostic message +unlink($node->data_dir . '/pg_hba.conf'); +$node->append_conf('pg_hba.conf', qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapprefix="?uid=" ldapsuffix=""}); +$node->reload; + +$ENV{"PGPASSWORD"} = 'secret1'; +test_access($node, 'test1', 2, 'any attempt fails due to bad search pattern'); + +unlink($node->data_dir . '/pg_hba.conf'); +$node->append_conf('pg_hba.conf', qq{local all all ldap ldapserver=$ldap_server ldapscheme=ldaps ldapport=$ldaps_port ldapbasedn="$ldap_basedn" ldapsearchfilter="(uid=\$username)"}); +$node->reload; + +$ENV{"PGPASSWORD"} = 'secret1'; +test_access($node, 'test1', 0, 'LDAPS'); + unlink($node->data_dir . '/pg_hba.conf'); -$node->append_conf('pg_hba.conf', qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapprefix="uid=" ldapsuffix=",dc=example,dc=net" ldaptls=1}); +$node->append_conf('pg_hba.conf', qq{local all all ldap ldapurl="$ldaps_url/$ldap_basedn??sub?(uid=\$username)"}); $node->reload; $ENV{"PGPASSWORD"} = 'secret1'; -test_access($node, 'test1', 2, 'any attempt fails due to unsupported TLS'); +test_access($node, 'test1', 0, 'LDAPS with URL'); -- 2.14.1