Skip to content

Commit 35c0754

Browse files
committed
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
1 parent 2268e6a commit 35c0754

File tree

8 files changed

+178
-24
lines changed

8 files changed

+178
-24
lines changed

configure

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10424,6 +10424,17 @@ fi
1042410424
else
1042510425
LDAP_LIBS_FE="-lldap $EXTRA_LDAP_LIBS"
1042610426
fi
10427+
for ac_func in ldap_initialize
10428+
do :
10429+
ac_fn_c_check_func "$LINENO" "ldap_initialize" "ac_cv_func_ldap_initialize"
10430+
if test "x$ac_cv_func_ldap_initialize" = xyes; then :
10431+
cat >>confdefs.h <<_ACEOF
10432+
#define HAVE_LDAP_INITIALIZE 1
10433+
_ACEOF
10434+
10435+
fi
10436+
done
10437+
1042710438
else
1042810439
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ldap_bind in -lwldap32" >&5
1042910440
$as_echo_n "checking for ldap_bind in -lwldap32... " >&6; }

configure.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,7 @@ if test "$with_ldap" = yes ; then
11061106
else
11071107
LDAP_LIBS_FE="-lldap $EXTRA_LDAP_LIBS"
11081108
fi
1109+
AC_CHECK_FUNCS([ldap_initialize])
11091110
else
11101111
AC_CHECK_LIB(wldap32, ldap_bind, [], [AC_MSG_ERROR([library 'wldap32' is required for LDAP])])
11111112
LDAP_LIBS_FE="-lwldap32"

doc/src/sgml/client-auth.sgml

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,19 +1502,40 @@ omicron bryanh guest1
15021502
</para>
15031503
</listitem>
15041504
</varlistentry>
1505+
<varlistentry>
1506+
<term><literal>ldapscheme</literal></term>
1507+
<listitem>
1508+
<para>
1509+
Set to <literal>ldaps</literal> to use LDAPS. This is a non-standard
1510+
way of using LDAP over SSL, supported by some LDAP server
1511+
implementations. See also the <literal>ldaptls</literal> option for
1512+
an alternative.
1513+
</para>
1514+
</listitem>
1515+
</varlistentry>
15051516
<varlistentry>
15061517
<term><literal>ldaptls</literal></term>
15071518
<listitem>
15081519
<para>
1509-
Set to 1 to make the connection between PostgreSQL and the
1510-
LDAP server use TLS encryption. Note that this only encrypts
1511-
the traffic to the LDAP server &mdash; the connection to the client
1512-
will still be unencrypted unless SSL is used.
1520+
Set to 1 to make the connection between PostgreSQL and the LDAP server
1521+
use TLS encryption. This uses the <literal>StartTLS</literal>
1522+
operation per RFC 4513. See also the <literal>ldapscheme</literal>
1523+
option for an alternative.
15131524
</para>
15141525
</listitem>
15151526
</varlistentry>
15161527
</variablelist>
1528+
</para>
1529+
1530+
<para>
1531+
Note that using <literal>ldapscheme</literal> or
1532+
<literal>ldaptls</literal> only encrypts the traffic between the
1533+
PostgreSQL server and the LDAP server. The connection between the
1534+
PostgreSQL server and the PostgreSQL client will still be unencrypted
1535+
unless SSL is used there as well.
1536+
</para>
15171537

1538+
<para>
15181539
The following options are used in simple bind mode only:
15191540
<variablelist>
15201541
<varlistentry>
@@ -1536,7 +1557,9 @@ omicron bryanh guest1
15361557
</listitem>
15371558
</varlistentry>
15381559
</variablelist>
1560+
</para>
15391561

1562+
<para>
15401563
The following options are used in search+bind mode only:
15411564
<variablelist>
15421565
<varlistentry>
@@ -1594,7 +1617,7 @@ omicron bryanh guest1
15941617
An RFC 4516 LDAP URL. This is an alternative way to write some of the
15951618
other LDAP options in a more compact and standard form. The format is
15961619
<synopsis>
1597-
ldap://<replaceable>host</replaceable>[:<replaceable>port</replaceable>]/<replaceable>basedn</replaceable>[?[<replaceable>attribute</replaceable>][?[<replaceable>scope</replaceable>][?[<replaceable>filter</replaceable>]]]]
1620+
ldap[s]://<replaceable>host</replaceable>[:<replaceable>port</replaceable>]/<replaceable>basedn</replaceable>[?[<replaceable>attribute</replaceable>][?[<replaceable>scope</replaceable>][?[<replaceable>filter</replaceable>]]]]
15981621
</synopsis>
15991622
<replaceable>scope</replaceable> must be one
16001623
of <literal>base</literal>, <literal>one</literal>, <literal>sub</literal>,
@@ -1608,16 +1631,19 @@ ldap://<replaceable>host</replaceable>[:<replaceable>port</replaceable>]/<replac
16081631
</para>
16091632

16101633
<para>
1611-
For non-anonymous binds, <literal>ldapbinddn</literal>
1612-
and <literal>ldapbindpasswd</literal> must be specified as separate
1613-
options.
1634+
The URL scheme <literal>ldaps</literal> chooses the LDAPS method for
1635+
making LDAP connections over SSL, equivalent to using
1636+
<literal>ldapscheme=ldaps</literal>. To use encrypted LDAP
1637+
connections using the <literal>StartTLS</literal> operation, use the
1638+
normal URL scheme <literal>ldap</literal> and specify the
1639+
<literal>ldaptls</literal> option in addition to
1640+
<literal>ldapurl</literal>.
16141641
</para>
16151642

16161643
<para>
1617-
To use encrypted LDAP connections, the <literal>ldaptls</literal>
1618-
option has to be used in addition to <literal>ldapurl</literal>.
1619-
The <literal>ldaps</literal> URL scheme (direct SSL connection) is not
1620-
supported.
1644+
For non-anonymous binds, <literal>ldapbinddn</literal>
1645+
and <literal>ldapbindpasswd</literal> must be specified as separate
1646+
options.
16211647
</para>
16221648

16231649
<para>

src/backend/libpq/auth.c

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2355,22 +2355,61 @@ static int errdetail_for_ldap(LDAP *ldap);
23552355
static int
23562356
InitializeLDAPConnection(Port *port, LDAP **ldap)
23572357
{
2358+
const char *scheme;
23582359
int ldapversion = LDAP_VERSION3;
23592360
int r;
23602361

2361-
*ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
2362+
scheme = port->hba->ldapscheme;
2363+
if (scheme == NULL)
2364+
scheme = "ldap";
2365+
#ifdef WIN32
2366+
*ldap = ldap_sslinit(port->hba->ldapserver,
2367+
port->hba->ldapport,
2368+
strcmp(scheme, "ldaps") == 0);
23622369
if (!*ldap)
23632370
{
2364-
#ifndef WIN32
2365-
ereport(LOG,
2366-
(errmsg("could not initialize LDAP: %m")));
2367-
#else
23682371
ereport(LOG,
23692372
(errmsg("could not initialize LDAP: error code %d",
23702373
(int) LdapGetLastError())));
2371-
#endif
2374+
2375+
return STATUS_ERROR;
2376+
}
2377+
#else
2378+
#ifdef HAVE_LDAP_INITIALIZE
2379+
{
2380+
char *uri;
2381+
2382+
uri = psprintf("%s://%s:%d", scheme, port->hba->ldapserver,
2383+
port->hba->ldapport);
2384+
r = ldap_initialize(ldap, uri);
2385+
pfree(uri);
2386+
if (r != LDAP_SUCCESS)
2387+
{
2388+
ereport(LOG,
2389+
(errmsg("could not initialize LDAP: %s",
2390+
ldap_err2string(r))));
2391+
2392+
return STATUS_ERROR;
2393+
}
2394+
}
2395+
#else
2396+
if (strcmp(scheme, "ldaps") == 0)
2397+
{
2398+
ereport(LOG,
2399+
(errmsg("ldaps not supported with this LDAP library")));
2400+
2401+
return STATUS_ERROR;
2402+
}
2403+
*ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
2404+
if (!*ldap)
2405+
{
2406+
ereport(LOG,
2407+
(errmsg("could not initialize LDAP: %m")));
2408+
23722409
return STATUS_ERROR;
23732410
}
2411+
#endif
2412+
#endif
23742413

23752414
if ((r = ldap_set_option(*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
23762415
{
@@ -2493,7 +2532,13 @@ CheckLDAPAuth(Port *port)
24932532
}
24942533

24952534
if (port->hba->ldapport == 0)
2496-
port->hba->ldapport = LDAP_PORT;
2535+
{
2536+
if (port->hba->ldapscheme != NULL &&
2537+
strcmp(port->hba->ldapscheme, "ldaps") == 0)
2538+
port->hba->ldapport = LDAPS_PORT;
2539+
else
2540+
port->hba->ldapport = LDAP_PORT;
2541+
}
24972542

24982543
sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
24992544

src/backend/libpq/hba.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1728,7 +1728,8 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
17281728
return false;
17291729
}
17301730

1731-
if (strcmp(urldata->lud_scheme, "ldap") != 0)
1731+
if (strcmp(urldata->lud_scheme, "ldap") != 0 &&
1732+
strcmp(urldata->lud_scheme, "ldaps") != 0)
17321733
{
17331734
ereport(elevel,
17341735
(errcode(ERRCODE_CONFIG_FILE_ERROR),
@@ -1739,6 +1740,8 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
17391740
return false;
17401741
}
17411742

1743+
if (urldata->lud_scheme)
1744+
hbaline->ldapscheme = pstrdup(urldata->lud_scheme);
17421745
if (urldata->lud_host)
17431746
hbaline->ldapserver = pstrdup(urldata->lud_host);
17441747
hbaline->ldapport = urldata->lud_port;
@@ -1766,6 +1769,17 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
17661769
else
17671770
hbaline->ldaptls = false;
17681771
}
1772+
else if (strcmp(name, "ldapscheme") == 0)
1773+
{
1774+
REQUIRE_AUTH_OPTION(uaLDAP, "ldapscheme", "ldap");
1775+
if (strcmp(val, "ldap") != 0 && strcmp(val, "ldaps") != 0)
1776+
ereport(elevel,
1777+
(errcode(ERRCODE_CONFIG_FILE_ERROR),
1778+
errmsg("invalid ldapscheme value: \"%s\"", val),
1779+
errcontext("line %d of configuration file \"%s\"",
1780+
line_num, HbaFileName)));
1781+
hbaline->ldapscheme = pstrdup(val);
1782+
}
17691783
else if (strcmp(name, "ldapserver") == 0)
17701784
{
17711785
REQUIRE_AUTH_OPTION(uaLDAP, "ldapserver", "ldap");

src/include/libpq/hba.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ typedef struct HbaLine
7575
char *pamservice;
7676
bool pam_use_hostname;
7777
bool ldaptls;
78+
char *ldapscheme;
7879
char *ldapserver;
7980
int ldapport;
8081
char *ldapbinddn;

src/include/pg_config.h.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,9 @@
310310
/* Define to 1 if you have the <ldap.h> header file. */
311311
#undef HAVE_LDAP_H
312312

313+
/* Define to 1 if you have the `ldap_initialize' function. */
314+
#undef HAVE_LDAP_INITIALIZE
315+
313316
/* Define to 1 if you have the `crypto' library (-lcrypto). */
314317
#undef HAVE_LIBCRYPTO
315318

src/test/ldap/t/001_auth.pl

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use warnings;
33
use TestLib;
44
use PostgresNode;
5-
use Test::More tests => 15;
5+
use Test::More tests => 19;
66

77
my ($slapd, $ldap_bin_dir, $ldap_schema_dir);
88

@@ -33,13 +33,16 @@
3333
$ENV{PATH} = "$ldap_bin_dir:$ENV{PATH}" if $ldap_bin_dir;
3434

3535
my $ldap_datadir = "${TestLib::tmp_check}/openldap-data";
36+
my $slapd_certs = "${TestLib::tmp_check}/slapd-certs";
3637
my $slapd_conf = "${TestLib::tmp_check}/slapd.conf";
3738
my $slapd_pidfile = "${TestLib::tmp_check}/slapd.pid";
3839
my $slapd_logfile = "${TestLib::tmp_check}/slapd.log";
3940
my $ldap_conf = "${TestLib::tmp_check}/ldap.conf";
4041
my $ldap_server = 'localhost';
4142
my $ldap_port = int(rand() * 16384) + 49152;
43+
my $ldaps_port = $ldap_port + 1;
4244
my $ldap_url = "ldap://$ldap_server:$ldap_port";
45+
my $ldaps_url = "ldaps://$ldap_server:$ldaps_port";
4346
my $ldap_basedn = 'dc=example,dc=net';
4447
my $ldap_rootdn = 'cn=Manager,dc=example,dc=net';
4548
my $ldap_rootpw = 'secret';
@@ -63,13 +66,27 @@
6366
database ldif
6467
directory $ldap_datadir
6568
69+
TLSCACertificateFile $slapd_certs/ca.crt
70+
TLSCertificateFile $slapd_certs/server.crt
71+
TLSCertificateKeyFile $slapd_certs/server.key
72+
6673
suffix "dc=example,dc=net"
6774
rootdn "$ldap_rootdn"
6875
rootpw $ldap_rootpw});
6976

77+
# don't bother to check the server's cert (though perhaps we should)
78+
append_to_file($ldap_conf,
79+
qq{TLS_REQCERT never
80+
});
81+
7082
mkdir $ldap_datadir or die;
83+
mkdir $slapd_certs or die;
84+
85+
system_or_bail "openssl", "req", "-new", "-nodes", "-keyout", "$slapd_certs/ca.key", "-x509", "-out", "$slapd_certs/ca.crt", "-subj", "/cn=CA";
86+
system_or_bail "openssl", "req", "-new", "-nodes", "-keyout", "$slapd_certs/server.key", "-out", "$slapd_certs/server.csr", "-subj", "/cn=server";
87+
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";
7188

72-
system_or_bail $slapd, '-f', $slapd_conf, '-h', $ldap_url;
89+
system_or_bail $slapd, '-f', $slapd_conf, '-h', "$ldap_url $ldaps_url";
7390

7491
END
7592
{
@@ -81,6 +98,7 @@ END
8198

8299
$ENV{'LDAPURI'} = $ldap_url;
83100
$ENV{'LDAPBINDDN'} = $ldap_rootdn;
101+
$ENV{'LDAPCONF'} = $ldap_conf;
84102

85103
note "loading LDAP data";
86104

@@ -178,9 +196,44 @@ sub test_access
178196

179197
note "diagnostic message";
180198

199+
# note bad ldapprefix with a question mark that triggers a diagnostic message
200+
unlink($node->data_dir . '/pg_hba.conf');
201+
$node->append_conf('pg_hba.conf', qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapprefix="?uid=" ldapsuffix=""});
202+
$node->reload;
203+
204+
$ENV{"PGPASSWORD"} = 'secret1';
205+
test_access($node, 'test1', 2, 'any attempt fails due to bad search pattern');
206+
207+
note "TLS";
208+
209+
# request StartTLS with ldaptls=1
210+
unlink($node->data_dir . '/pg_hba.conf');
211+
$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});
212+
$node->reload;
213+
214+
$ENV{"PGPASSWORD"} = 'secret1';
215+
test_access($node, 'test1', 0, 'StartTLS');
216+
217+
# request LDAPS with ldapscheme=ldaps
218+
unlink($node->data_dir . '/pg_hba.conf');
219+
$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)"});
220+
$node->reload;
221+
222+
$ENV{"PGPASSWORD"} = 'secret1';
223+
test_access($node, 'test1', 0, 'LDAPS');
224+
225+
# request LDAPS with ldapurl=ldaps://...
226+
unlink($node->data_dir . '/pg_hba.conf');
227+
$node->append_conf('pg_hba.conf', qq{local all all ldap ldapurl="$ldaps_url/$ldap_basedn??sub?(uid=\$username)"});
228+
$node->reload;
229+
230+
$ENV{"PGPASSWORD"} = 'secret1';
231+
test_access($node, 'test1', 0, 'LDAPS with URL');
232+
233+
# bad combination of LDAPS and StartTLS
181234
unlink($node->data_dir . '/pg_hba.conf');
182-
$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});
235+
$node->append_conf('pg_hba.conf', qq{local all all ldap ldapurl="$ldaps_url/$ldap_basedn??sub?(uid=\$username)" ldaptls=1});
183236
$node->reload;
184237

185238
$ENV{"PGPASSWORD"} = 'secret1';
186-
test_access($node, 'test1', 2, 'any attempt fails due to unsupported TLS');
239+
test_access($node, 'test1', 2, 'bad combination of LDAPS and StartTLS');

0 commit comments

Comments
 (0)