aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Eisentraut <peter_e@gmx.net>2018-01-03 10:00:08 -0500
committerPeter Eisentraut <peter_e@gmx.net>2018-01-03 10:11:26 -0500
commit35c0754fadca8010955f6b10cb47af00bdbe1286 (patch)
tree0df537278cc8998dbe29941d43833d95306e217e
parent2268e6afd59649d6bf6d114a19e9c492d59b43fc (diff)
downloadpostgresql-35c0754fadca8010955f6b10cb47af00bdbe1286.tar.gz
postgresql-35c0754fadca8010955f6b10cb47af00bdbe1286.zip
Allow ldaps when using ldap authentication
While ldaptls=1 provides an RFC 4513 conforming way to do LDAP authentication with TLS 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 still widely used and sometimes required by organizations' network policies. There seems to be no reason not to support it when available in the client library. Therefore, add support when using OpenLDAP 2.4+ or Windows. It can be configured with ldapscheme=ldaps or ldapurl=ldaps://... Add tests for both ways of requesting LDAPS and a test for the pre-existing ldaptls=1. Modify the 001_auth.pl test for "diagnostic messages", which was previously relying on the server rejecting ldaptls=1. Author: Thomas Munro Reviewed-By: Peter Eisentraut Discussion: https://postgr.es/m/CAEepm=1s+pA-LZUjQ-9GQz0Z4rX_eK=DFXAF1nBQ+ROPimuOYQ@mail.gmail.com
-rwxr-xr-xconfigure11
-rw-r--r--configure.in1
-rw-r--r--doc/src/sgml/client-auth.sgml50
-rw-r--r--src/backend/libpq/auth.c59
-rw-r--r--src/backend/libpq/hba.c16
-rw-r--r--src/include/libpq/hba.h1
-rw-r--r--src/include/pg_config.h.in3
-rw-r--r--src/test/ldap/t/001_auth.pl61
8 files changed, 178 insertions, 24 deletions
diff --git a/configure b/configure
index 82f332f545c..d88863e50cf 100755
--- a/configure
+++ b/configure
@@ -10424,6 +10424,17 @@ fi
else
LDAP_LIBS_FE="-lldap $EXTRA_LDAP_LIBS"
fi
+ for ac_func in ldap_initialize
+do :
+ ac_fn_c_check_func "$LINENO" "ldap_initialize" "ac_cv_func_ldap_initialize"
+if test "x$ac_cv_func_ldap_initialize" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LDAP_INITIALIZE 1
+_ACEOF
+
+fi
+done
+
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ldap_bind in -lwldap32" >&5
$as_echo_n "checking for ldap_bind in -lwldap32... " >&6; }
diff --git a/configure.in b/configure.in
index 4a20c9da965..4968b67bf92 100644
--- a/configure.in
+++ b/configure.in
@@ -1106,6 +1106,7 @@ if test "$with_ldap" = yes ; then
else
LDAP_LIBS_FE="-lldap $EXTRA_LDAP_LIBS"
fi
+ AC_CHECK_FUNCS([ldap_initialize])
else
AC_CHECK_LIB(wldap32, ldap_bind, [], [AC_MSG_ERROR([library 'wldap32' is required for LDAP])])
LDAP_LIBS_FE="-lwldap32"
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index c8a1bc79aa6..53832d08e29 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -1503,18 +1503,39 @@ omicron bryanh guest1
</listitem>
</varlistentry>
<varlistentry>
+ <term><literal>ldapscheme</literal></term>
+ <listitem>
+ <para>
+ Set to <literal>ldaps</literal> to use LDAPS. This is a non-standard
+ way of using LDAP over SSL, supported by some LDAP server
+ implementations. See also the <literal>ldaptls</literal> option for
+ an alternative.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
<term><literal>ldaptls</literal></term>
<listitem>
<para>
- Set to 1 to make the connection between PostgreSQL and the
- LDAP server use TLS encryption. Note that this only encrypts
- the traffic to the LDAP server &mdash; the connection to the client
- will still be unencrypted unless SSL is used.
+ Set to 1 to make the connection between PostgreSQL and the LDAP server
+ use TLS encryption. This uses the <literal>StartTLS</literal>
+ operation per RFC 4513. See also the <literal>ldapscheme</literal>
+ option for an alternative.
</para>
</listitem>
</varlistentry>
</variablelist>
+ </para>
+
+ <para>
+ Note that using <literal>ldapscheme</literal> or
+ <literal>ldaptls</literal> only encrypts the traffic between the
+ PostgreSQL server and the LDAP server. The connection between the
+ PostgreSQL server and the PostgreSQL client will still be unencrypted
+ unless SSL is used there as well.
+ </para>
+ <para>
The following options are used in simple bind mode only:
<variablelist>
<varlistentry>
@@ -1536,7 +1557,9 @@ omicron bryanh guest1
</listitem>
</varlistentry>
</variablelist>
+ </para>
+ <para>
The following options are used in search+bind mode only:
<variablelist>
<varlistentry>
@@ -1594,7 +1617,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
<synopsis>
-ldap://<replaceable>host</replaceable>[:<replaceable>port</replaceable>]/<replaceable>basedn</replaceable>[?[<replaceable>attribute</replaceable>][?[<replaceable>scope</replaceable>][?[<replaceable>filter</replaceable>]]]]
+ldap[s]://<replaceable>host</replaceable>[:<replaceable>port</replaceable>]/<replaceable>basedn</replaceable>[?[<replaceable>attribute</replaceable>][?[<replaceable>scope</replaceable>][?[<replaceable>filter</replaceable>]]]]
</synopsis>
<replaceable>scope</replaceable> must be one
of <literal>base</literal>, <literal>one</literal>, <literal>sub</literal>,
@@ -1608,16 +1631,19 @@ ldap://<replaceable>host</replaceable>[:<replaceable>port</replaceable>]/<replac
</para>
<para>
- For non-anonymous binds, <literal>ldapbinddn</literal>
- and <literal>ldapbindpasswd</literal> must be specified as separate
- options.
+ The URL scheme <literal>ldaps</literal> chooses the LDAPS method for
+ making LDAP connections over SSL, equivalent to using
+ <literal>ldapscheme=ldaps</literal>. To use encrypted LDAP
+ connections using the <literal>StartTLS</literal> operation, use the
+ normal URL scheme <literal>ldap</literal> and specify the
+ <literal>ldaptls</literal> option in addition to
+ <literal>ldapurl</literal>.
</para>
<para>
- To use encrypted LDAP connections, the <literal>ldaptls</literal>
- option has to be used in addition to <literal>ldapurl</literal>.
- The <literal>ldaps</literal> URL scheme (direct SSL connection) is not
- supported.
+ For non-anonymous binds, <literal>ldapbinddn</literal>
+ and <literal>ldapbindpasswd</literal> must be specified as separate
+ options.
</para>
<para>
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 1d49ed784f6..3560edc33a0 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2355,22 +2355,61 @@ static int errdetail_for_ldap(LDAP *ldap);
static int
InitializeLDAPConnection(Port *port, LDAP **ldap)
{
+ const char *scheme;
int ldapversion = LDAP_VERSION3;
int r;
- *ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
+ scheme = port->hba->ldapscheme;
+ if (scheme == NULL)
+ scheme = "ldap";
+#ifdef WIN32
+ *ldap = ldap_sslinit(port->hba->ldapserver,
+ port->hba->ldapport,
+ strcmp(scheme, "ldaps") == 0);
if (!*ldap)
{
-#ifndef WIN32
- ereport(LOG,
- (errmsg("could not initialize LDAP: %m")));
-#else
ereport(LOG,
(errmsg("could not initialize LDAP: error code %d",
(int) LdapGetLastError())));
-#endif
+
+ return STATUS_ERROR;
+ }
+#else
+#ifdef HAVE_LDAP_INITIALIZE
+ {
+ char *uri;
+
+ uri = psprintf("%s://%s:%d", scheme, port->hba->ldapserver,
+ port->hba->ldapport);
+ r = ldap_initialize(ldap, uri);
+ pfree(uri);
+ if (r != LDAP_SUCCESS)
+ {
+ ereport(LOG,
+ (errmsg("could not initialize LDAP: %s",
+ ldap_err2string(r))));
+
+ return STATUS_ERROR;
+ }
+ }
+#else
+ if (strcmp(scheme, "ldaps") == 0)
+ {
+ ereport(LOG,
+ (errmsg("ldaps not supported with this LDAP library")));
+
+ return STATUS_ERROR;
+ }
+ *ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
+ if (!*ldap)
+ {
+ ereport(LOG,
+ (errmsg("could not initialize LDAP: %m")));
+
return STATUS_ERROR;
}
+#endif
+#endif
if ((r = ldap_set_option(*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
{
@@ -2493,7 +2532,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 f760d24886d..aa20f266b8d 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,8 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
return false;
}
+ if (urldata->lud_scheme)
+ hbaline->ldapscheme = pstrdup(urldata->lud_scheme);
if (urldata->lud_host)
hbaline->ldapserver = pstrdup(urldata->lud_host);
hbaline->ldapport = urldata->lud_port;
@@ -1766,6 +1769,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 e711bee8bff..5f68f4c6661 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/include/pg_config.h.in b/src/include/pg_config.h.in
index 0aa6be46665..27b13687211 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -310,6 +310,9 @@
/* Define to 1 if you have the <ldap.h> header file. */
#undef HAVE_LDAP_H
+/* Define to 1 if you have the `ldap_initialize' function. */
+#undef HAVE_LDAP_INITIALIZE
+
/* Define to 1 if you have the `crypto' library (-lcrypto). */
#undef HAVE_LIBCRYPTO
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 38760ece617..5508da459f3 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 => 19;
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 "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;
+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,44 @@ 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');
+
+note "TLS";
+
+# request StartTLS with ldaptls=1
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf', qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapbasedn="$ldap_basedn" ldapsearchfilter="(uid=\$username)" ldaptls=1});
+$node->reload;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'StartTLS');
+
+# request LDAPS with ldapscheme=ldaps
+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');
+
+# request LDAPS with ldapurl=ldaps://...
+unlink($node->data_dir . '/pg_hba.conf');
+$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', 0, 'LDAPS with URL');
+
+# bad combination of LDAPS and StartTLS
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)" ldaptls=1});
$node->reload;
$ENV{"PGPASSWORD"} = 'secret1';
-test_access($node, 'test1', 2, 'any attempt fails due to unsupported TLS');
+test_access($node, 'test1', 2, 'bad combination of LDAPS and StartTLS');