On Tue, 2021-09-28 at 18:15 +0000, Jacob Champion wrote:
> | authn authz
> ---------+-----------------------------------
> envvar | PGAUTHUSER PGUSER
> conninfo | authuser user
> frontend | conn->pgauthuser conn->pguser
> backend | port->auth_user port->user_name
v3 attached, which uses the above naming scheme and removes the stale
TODO. Changes in since-v2.
--Jacob
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out
b/contrib/postgres_fdw/expected/postgres_fdw.out
index 838ca83b55..cf74aeac41 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -188,7 +188,7 @@ ALTER USER MAPPING FOR public SERVER testserver1
ALTER USER MAPPING FOR public SERVER testserver1
OPTIONS (ADD sslmode 'require');
ERROR: invalid option "sslmode"
-HINT: Valid options in this context are: user, password, sslpassword,
ldapuser, password_required, sslcert, sslkey
+HINT: Valid options in this context are: user, password, sslpassword,
authuser, password_required, sslcert, sslkey
-- But we can add valid ones fine
ALTER USER MAPPING FOR public SERVER testserver1
OPTIONS (ADD sslpassword 'dummy');
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index c734467f01..310e30598c 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -315,11 +315,11 @@ InitPgFdwOptions(void)
popt->keyword = lopt->keyword;
/*
- * "user", "ldapuser", and any secret options are allowed only
on user
+ * "user", "authuser", and any secret options are allowed only
on user
* mappings. Everything else is a server option.
*/
if (strcmp(lopt->keyword, "user") == 0 ||
- strcmp(lopt->keyword, "ldapuser") == 0 ||
+ strcmp(lopt->keyword, "authuser") == 0 ||
strchr(lopt->dispchar, '*'))
popt->optcontext = UserMappingRelationId;
else
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 8a0ec715e4..9e62d36fb4 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1455,8 +1455,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
- <varlistentry id="libpq-connect-ldapuser" xreflabel="ldapuser">
- <term><literal>ldapuser</literal></term>
+ <varlistentry id="libpq-connect-authuser" xreflabel="authuser">
+ <term><literal>authuser</literal></term>
<listitem>
<para>
When connecting to servers that use LDAP authentication, this option
@@ -7943,10 +7943,10 @@ myEventProc(PGEventId evtId, void *evtInfo, void
*passThrough)
<listitem>
<para>
<indexterm>
- <primary><envar>PGLDAPUSER</envar></primary>
+ <primary><envar>PGAUTHUSER</envar></primary>
</indexterm>
- <envar>PGLDAPUSER</envar> behaves the same as the
- <xref linkend="libpq-connect-ldapuser"/> connection parameter.
+ <envar>PGAUTHUSER</envar> behaves the same as the
+ <xref linkend="libpq-connect-authuser"/> connection parameter.
</para>
</listitem>
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 1165550955..4286dcfc47 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -120,7 +120,7 @@
<itemizedlist spacing="compact">
<listitem>
<para>
- <literal>user</literal>, <literal>ldapuser</literal>,
+ <literal>user</literal>, <literal>authuser</literal>,
<literal>password</literal> and <literal>sslpassword</literal> (specify
these
in a user mapping, instead, or use a service file)
</para>
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index e2010fa402..5205bc1a53 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2534,8 +2534,8 @@ CheckLDAPAuth(Port *port)
return STATUS_ERROR;
}
- /* If a PGLDAPUSER was not provided, default to PGUSER. */
- ldapuser = port->ldapuser;
+ /* If a PGAUTHUSER was not provided, default to PGUSER. */
+ ldapuser = port->auth_user;
if (!ldapuser || !ldapuser[0])
ldapuser = port->user_name;
diff --git a/src/backend/postmaster/postmaster.c
b/src/backend/postmaster/postmaster.c
index bc7e727dc5..e34d6f5726 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2208,8 +2208,8 @@ retry1:
port->database_name = pstrdup(valptr);
else if (strcmp(nameptr, "user") == 0)
port->user_name = pstrdup(valptr);
- else if (strcmp(nameptr, "ldapuser") == 0)
- port->ldapuser = pstrdup(valptr);
+ else if (strcmp(nameptr, "authuser") == 0)
+ port->auth_user = pstrdup(valptr);
else if (strcmp(nameptr, "options") == 0)
port->cmdline_options = pstrdup(valptr);
else if (strcmp(nameptr, "replication") == 0)
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 26f764aae9..ae9332cd45 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -147,6 +147,12 @@ typedef struct Port
char *cmdline_options;
List *guc_options;
+ /*
+ * The username to use during authentication, if different from
user_name,
+ * or else NULL. Currently only supported for the LDAP auth method.
+ */
+ const char *auth_user;
+
/*
* The startup packet application name, only used here for the
"connection
* authorized" log message. We shouldn't use this post-startup, instead
@@ -203,11 +209,6 @@ typedef struct Port
void *gss;
#endif
- /*
- * LDAP structures.
- */
- const char *ldapuser;
-
/*
* SSL structures.
*/
diff --git a/src/interfaces/libpq/fe-connect.c
b/src/interfaces/libpq/fe-connect.c
index 6c7a3a94cb..75d0d7d4ec 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,9 +344,9 @@ static const internalPQconninfoOption PQconninfoOptions[] =
{
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") =
15 */
offsetof(struct pg_conn, target_session_attrs)},
- {"ldapuser", "PGLDAPUSER", NULL, NULL,
- "LDAP-User", "", 20,
- offsetof(struct pg_conn, pgldapuser)},
+ {"authuser", "PGAUTHUSER", NULL, NULL,
+ "Auth-User", "", 20,
+ offsetof(struct pg_conn, pgauthuser)},
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
@@ -1440,8 +1440,6 @@ connectOptions2(PGconn *conn)
goto oom_error;
}
- /* TODO: unset pgldapuser if it's the same as pguser, for
compatibility? */
-
/*
* Only if we get this far is it appropriate to try to connect. (We
need a
* state flag, rather than just the boolean result of this function, in
diff --git a/src/interfaces/libpq/fe-protocol3.c
b/src/interfaces/libpq/fe-protocol3.c
index 42e4e535ea..308fdcdf6f 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -2220,8 +2220,8 @@ build_startup_packet(const PGconn *conn, char *packet,
ADD_STARTUP_OPTION("replication", conn->replication);
if (conn->pgoptions && conn->pgoptions[0])
ADD_STARTUP_OPTION("options", conn->pgoptions);
- if (conn->pgldapuser && conn->pgldapuser[0])
- ADD_STARTUP_OPTION("ldapuser", conn->pgldapuser);
+ if (conn->pgauthuser && conn->pgauthuser[0])
+ ADD_STARTUP_OPTION("authuser", conn->pgauthuser);
if (conn->send_appname)
{
/* Use appname if present, otherwise use fallback */
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index ea8e23da08..288e93f367 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -390,7 +390,7 @@ struct pg_conn
char *krbsrvname; /* Kerberos service name */
char *gsslib; /* What GSS library to use
("gssapi" or
* "sspi") */
- char *pgldapuser; /* LDAP username, if not pguser */
+ char *pgauthuser; /* LDAP username, if not pguser */
char *ssl_min_protocol_version; /* minimum TLS protocol version
*/
char *ssl_max_protocol_version; /* maximum TLS protocol version
*/
char *target_session_attrs; /* desired session properties */
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 4a771e1b79..963d4a97ce 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -246,15 +246,15 @@ test_access(
qr/no match in usermap "mymap" for user "test2" authenticated
as "test2"/,
]);
-$ENV{"PGLDAPUSER"} = 'test2';
+$ENV{"PGAUTHUSER"} = 'test2';
test_access(
$node, 'test1', 0,
- 'succeeds with different PGLDAPUSER',
+ 'succeeds with different PGAUTHUSER',
log_like => [
qr/connection authenticated:
identity="uid=test2,dc=example,dc=net" method=ldap/,
qr/connection authorized: user=test1/,
]);
-delete $ENV{"PGLDAPUSER"};
+delete $ENV{"PGAUTHUSER"};
unlink($node->data_dir . '/pg_hba.conf');
$node->append_conf('pg_hba.conf',
@@ -339,15 +339,15 @@ test_access(
qr/no match in usermap "mymap" for user "test2" authenticated
as "test2"/,
]);
-$ENV{"PGLDAPUSER"} = 'test2';
+$ENV{"PGAUTHUSER"} = 'test2';
test_access(
$node, 'test1', 0,
- 'succeeds with different PGLDAPUSER',
+ 'succeeds with different PGAUTHUSER',
log_like => [
qr/connection authenticated:
identity="uid=test2,dc=example,dc=net" method=ldap/,
qr/connection authorized: user=test1/,
]);
-delete $ENV{"PGLDAPUSER"};
+delete $ENV{"PGAUTHUSER"};
unlink($node->data_dir . '/pg_hba.conf');
$node->append_conf('pg_hba.conf',
From e2d1c987a363e486e4e4457d928ca393dbbc6eb3 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Mon, 30 Aug 2021 16:29:59 -0700
Subject: [PATCH v3] Allow user name mapping with LDAP
Enable the `map` HBA option for the ldap auth method. To make effective
use of this from the client side, the authuser connection option (and a
corresponding environment variable, PGAUTHUSER) has been added; it
defaults to the PGUSER.
For more advanced mapping, the ldap_map_dn HBA option can be set to use
the full user Distinguished Name during user mapping. (This parallels
the include_realm=1 and clientname=DN options.)
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
contrib/postgres_fdw/option.c | 8 +-
doc/src/sgml/client-auth.sgml | 45 +++++-
doc/src/sgml/libpq.sgml | 32 ++++
doc/src/sgml/postgres-fdw.sgml | 3 +-
src/backend/libpq/auth.c | 38 +++--
src/backend/libpq/hba.c | 29 +++-
src/backend/postmaster/postmaster.c | 2 +
src/include/libpq/hba.h | 1 +
src/include/libpq/libpq-be.h | 6 +
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-protocol3.c | 2 +
src/interfaces/libpq/libpq-int.h | 1 +
src/test/ldap/t/001_auth.pl | 145 +++++++++++++++++-
14 files changed, 298 insertions(+), 20 deletions(-)
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index fd141a0fa5..cf74aeac41 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -188,7 +188,7 @@ ALTER USER MAPPING FOR public SERVER testserver1
ALTER USER MAPPING FOR public SERVER testserver1
OPTIONS (ADD sslmode 'require');
ERROR: invalid option "sslmode"
-HINT: Valid options in this context are: user, password, sslpassword, password_required, sslcert, sslkey
+HINT: Valid options in this context are: user, password, sslpassword, authuser, password_required, sslcert, sslkey
-- But we can add valid ones fine
ALTER USER MAPPING FOR public SERVER testserver1
OPTIONS (ADD sslpassword 'dummy');
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 48c7417e6e..310e30598c 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -315,10 +315,12 @@ InitPgFdwOptions(void)
popt->keyword = lopt->keyword;
/*
- * "user" and any secret options are allowed only on user mappings.
- * Everything else is a server option.
+ * "user", "authuser", and any secret options are allowed only on user
+ * mappings. Everything else is a server option.
*/
- if (strcmp(lopt->keyword, "user") == 0 || strchr(lopt->dispchar, '*'))
+ if (strcmp(lopt->keyword, "user") == 0 ||
+ strcmp(lopt->keyword, "authuser") == 0 ||
+ strchr(lopt->dispchar, '*'))
popt->optcontext = UserMappingRelationId;
else
popt->optcontext = ForeignServerRelationId;
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 02f0489112..d902eb9d01 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -1624,10 +1624,11 @@ omicron bryanh guest1
This authentication method operates similarly to
<literal>password</literal> except that it uses LDAP
as the password verification method. LDAP is used only to validate
- the user name/password pairs. Therefore the user must already
- exist in the database before LDAP can be used for
- authentication.
- </para>
+ the user name/password pairs. Therefore database users must already
+ exist in the database before LDAP can be used for authentication. User name
+ mapping can be used to allow the LDAP user name to be different from the
+ database user name.
+</para>
<para>
LDAP authentication can operate in two modes. In the first mode,
@@ -1703,6 +1704,42 @@ omicron bryanh guest1
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>map</literal></term>
+ <listitem>
+ <para>
+ Allows for mapping between LDAP and database user names. See
+ <xref linkend="auth-username-maps"/> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>ldap_map_dn</literal></term>
+ <listitem>
+ <para>
+ Set to 1 to use the user's full Distinguished Name (e.g.
+ <literal>uid=someuser,dc=example,dc=com</literal>) during user name
+ mapping. When set to 0 (the default), only the bare username (in this
+ example, <literal>someuser</literal>) will be mapped. This option may
+ only be used in conjunction with the <literal>map</literal> option.
+ </para>
+ <note>
+ <para>
+ When using regular expression matching in combination with this option,
+ care should be taken to correctly anchor the regular expression in
+ order to avoid false positives. For example, the anchored expression
+ <literal>/,dc=example,dc=com$</literal> will match only DNs underneath
+ the <literal>example.com</literal> subtree, whereas the unanchored
+ <literal>/dc=example,dc=com</literal> could match
+ <literal>uid=mal,dc=example,dc=community,dc=invalid</literal>
+ or any number of DNs that are not rooted at
+ <literal>example.com</literal>. (Whether these DNs exist in the LDAP
+ tree to be abused is dependent on the LDAP deployment and outside the
+ scope of this documentation.)
+ </para>
+ </note>
+ </listitem>
+ </varlistentry>
</variablelist>
</para>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b449c834a9..9e62d36fb4 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1455,6 +1455,28 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-authuser" xreflabel="authuser">
+ <term><literal>authuser</literal></term>
+ <listitem>
+ <para>
+ When connecting to servers that use LDAP authentication, this option
+ sets the username that should be used to bind to the LDAP server.
+ By default, the <productname>PostgreSQL</productname> user name
+ (<xref linkend="libpq-connect-user"/>) is used to bind.
+ </para>
+
+ <note>
+ <para>
+ For this option to be useful, the server must be appropriately
+ configured with a user name map (<xref linkend="auth-username-maps"/>).
+ Server versions prior to 15 always use the
+ <productname>PostgreSQL</productname> user name to bind and cannot make
+ use of this option.
+ </para>
+ </note>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-gssencmode" xreflabel="gssencmode">
<term><literal>gssencmode</literal></term>
<listitem>
@@ -7918,6 +7940,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
</para>
</listitem>
+ <listitem>
+ <para>
+ <indexterm>
+ <primary><envar>PGAUTHUSER</envar></primary>
+ </indexterm>
+ <envar>PGAUTHUSER</envar> behaves the same as the
+ <xref linkend="libpq-connect-authuser"/> connection parameter.
+ </para>
+ </listitem>
+
<listitem>
<para>
<indexterm>
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 41c952fbe3..4286dcfc47 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -120,7 +120,8 @@
<itemizedlist spacing="compact">
<listitem>
<para>
- <literal>user</literal>, <literal>password</literal> and <literal>sslpassword</literal> (specify these
+ <literal>user</literal>, <literal>authuser</literal>,
+ <literal>password</literal> and <literal>sslpassword</literal> (specify these
in a user mapping, instead, or use a service file)
</para>
</listitem>
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index a317aef1c9..5205bc1a53 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2479,8 +2479,10 @@ CheckLDAPAuth(Port *port)
char *passwd;
LDAP *ldap;
int r;
+ const char *ldapuser;
char *fulluser;
const char *server_name;
+ const char *map_name;
#ifdef HAVE_LDAP_INITIALIZE
@@ -2532,6 +2534,11 @@ CheckLDAPAuth(Port *port)
return STATUS_ERROR;
}
+ /* If a PGAUTHUSER was not provided, default to PGUSER. */
+ ldapuser = port->auth_user;
+ if (!ldapuser || !ldapuser[0])
+ ldapuser = port->user_name;
+
if (port->hba->ldapbasedn)
{
/*
@@ -2543,7 +2550,7 @@ CheckLDAPAuth(Port *port)
LDAPMessage *entry;
char *attributes[] = {LDAP_NO_ATTRS, NULL};
char *dn;
- char *c;
+ const char *c;
int count;
/*
@@ -2552,7 +2559,7 @@ CheckLDAPAuth(Port *port)
* them would make it possible to inject any kind of custom filters in
* the LDAP filter.
*/
- for (c = port->user_name; *c; c++)
+ for (c = ldapuser; *c; c++)
{
if (*c == '*' ||
*c == '(' ||
@@ -2590,11 +2597,11 @@ CheckLDAPAuth(Port *port)
/* Build a custom filter or a single attribute filter? */
if (port->hba->ldapsearchfilter)
- filter = FormatSearchFilter(port->hba->ldapsearchfilter, port->user_name);
+ filter = FormatSearchFilter(port->hba->ldapsearchfilter, ldapuser);
else if (port->hba->ldapsearchattribute)
- filter = psprintf("(%s=%s)", port->hba->ldapsearchattribute, port->user_name);
+ filter = psprintf("(%s=%s)", port->hba->ldapsearchattribute, ldapuser);
else
- filter = psprintf("(uid=%s)", port->user_name);
+ filter = psprintf("(uid=%s)", ldapuser);
r = ldap_search_s(ldap,
port->hba->ldapbasedn,
@@ -2621,12 +2628,12 @@ CheckLDAPAuth(Port *port)
{
if (count == 0)
ereport(LOG,
- (errmsg("LDAP user \"%s\" does not exist", port->user_name),
+ (errmsg("LDAP user \"%s\" does not exist", ldapuser),
errdetail("LDAP search for filter \"%s\" on server \"%s\" returned no entries.",
filter, server_name)));
else
ereport(LOG,
- (errmsg("LDAP user \"%s\" is not unique", port->user_name),
+ (errmsg("LDAP user \"%s\" is not unique", ldapuser),
errdetail_plural("LDAP search for filter \"%s\" on server \"%s\" returned %d entry.",
"LDAP search for filter \"%s\" on server \"%s\" returned %d entries.",
count,
@@ -2691,7 +2698,7 @@ CheckLDAPAuth(Port *port)
else
fulluser = psprintf("%s%s%s",
port->hba->ldapprefix ? port->hba->ldapprefix : "",
- port->user_name,
+ ldapuser,
port->hba->ldapsuffix ? port->hba->ldapsuffix : "");
r = ldap_simple_bind_s(ldap, fulluser, passwd);
@@ -2713,9 +2720,22 @@ CheckLDAPAuth(Port *port)
ldap_unbind(ldap);
pfree(passwd);
+
+ /*
+ * Check the usermap for authorization. If ldap_map_dn is set and the admin
+ * has set up an explicit map, we use the full distinguised name during the
+ * mapping. Otherwise we just check the bare LDAP username.
+ */
+ map_name = ldapuser;
+ if (port->hba->usermap && port->hba->usermap[0] && port->hba->ldap_map_dn)
+ map_name = fulluser;
+
+ r = check_usermap(port->hba->usermap, port->user_name, map_name,
+ pg_krb_caseins_users);
+
pfree(fulluser);
- return STATUS_OK;
+ return r;
}
/*
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 3be8778d21..da9e1ae509 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1596,6 +1596,24 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
*err_msg = "cannot use ldapsearchattribute together with ldapsearchfilter";
return NULL;
}
+
+ /*
+ * Setting ldap_map_dn=1 tells the server to use the full DN when
+ * mapping users, but it doesn't do anything if there's no usermap
+ * defined. To prevent confusion, don't allow this to be set without a
+ * map.
+ */
+ if (parsedline->ldap_map_dn
+ && !(parsedline->usermap && parsedline->usermap[0]))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("authentication option \"ldap_map_dn\" requires argument \"map\" to be set"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, HbaFileName)));
+ *err_msg = "authentication option \"ldap_map_dn\" requires argument \"map\" to be set";
+ return NULL;
+ }
}
if (parsedline->auth_method == uaRADIUS)
@@ -1713,8 +1731,9 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
hbaline->auth_method != uaPeer &&
hbaline->auth_method != uaGSS &&
hbaline->auth_method != uaSSPI &&
+ hbaline->auth_method != uaLDAP &&
hbaline->auth_method != uaCert)
- INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, and cert"));
+ INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, ldap, and cert"));
hbaline->usermap = pstrdup(val);
}
else if (strcmp(name, "clientcert") == 0)
@@ -1931,6 +1950,14 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
REQUIRE_AUTH_OPTION(uaLDAP, "ldapsuffix", "ldap");
hbaline->ldapsuffix = pstrdup(val);
}
+ else if (strcmp(name, "ldap_map_dn") == 0)
+ {
+ REQUIRE_AUTH_OPTION(uaLDAP, "ldap_map_dn", "ldap");
+ if (strcmp(val, "1") == 0)
+ hbaline->ldap_map_dn = true;
+ else
+ hbaline->ldap_map_dn = false;
+ }
else if (strcmp(name, "krb_realm") == 0)
{
if (hbaline->auth_method != uaGSS &&
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index e2a76ba055..e34d6f5726 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2208,6 +2208,8 @@ retry1:
port->database_name = pstrdup(valptr);
else if (strcmp(nameptr, "user") == 0)
port->user_name = pstrdup(valptr);
+ else if (strcmp(nameptr, "authuser") == 0)
+ port->auth_user = pstrdup(valptr);
else if (strcmp(nameptr, "options") == 0)
port->cmdline_options = pstrdup(valptr);
else if (strcmp(nameptr, "replication") == 0)
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 8d9f3821b1..8729056f09 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -106,6 +106,7 @@ typedef struct HbaLine
int ldapscope;
char *ldapprefix;
char *ldapsuffix;
+ bool ldap_map_dn;
ClientCertMode clientcert;
ClientCertName clientcertname;
char *krb_realm;
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..ae9332cd45 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -147,6 +147,12 @@ typedef struct Port
char *cmdline_options;
List *guc_options;
+ /*
+ * The username to use during authentication, if different from user_name,
+ * or else NULL. Currently only supported for the LDAP auth method.
+ */
+ const char *auth_user;
+
/*
* The startup packet application name, only used here for the "connection
* authorized" log message. We shouldn't use this post-startup, instead
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index b288d346f9..75d0d7d4ec 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"authuser", "PGAUTHUSER", NULL, NULL,
+ "Auth-User", "", 20,
+ offsetof(struct pg_conn, pgauthuser)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 9ab3bf1fcb..308fdcdf6f 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -2220,6 +2220,8 @@ build_startup_packet(const PGconn *conn, char *packet,
ADD_STARTUP_OPTION("replication", conn->replication);
if (conn->pgoptions && conn->pgoptions[0])
ADD_STARTUP_OPTION("options", conn->pgoptions);
+ if (conn->pgauthuser && conn->pgauthuser[0])
+ ADD_STARTUP_OPTION("authuser", conn->pgauthuser);
if (conn->send_appname)
{
/* Use appname if present, otherwise use fallback */
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 490458adef..288e93f367 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -390,6 +390,7 @@ struct pg_conn
char *krbsrvname; /* Kerberos service name */
char *gsslib; /* What GSS library to use ("gssapi" or
* "sspi") */
+ char *pgauthuser; /* LDAP username, if not pguser */
char *ssl_min_protocol_version; /* minimum TLS protocol version */
char *ssl_max_protocol_version; /* maximum TLS protocol version */
char *target_session_attrs; /* desired session properties */
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 5a9a009832..963d4a97ce 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 60;
}
else
{
@@ -210,6 +210,77 @@ test_access(
qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/
],);
+note "ident mapping";
+
+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" map=mymap}
+);
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access(
+ $node, 'test1', 2,
+ 'fails without mapping',
+ log_like => [
+ qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/,
+ qr/no match in usermap "mymap" for user "test1"/,
+ ]);
+
+$node->append_conf('pg_ident.conf', qq{mymap /^ test1});
+$node->restart;
+
+test_access(
+ $node, 'test1', 0,
+ 'succeeds with mapping',
+ log_like => [
+ qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/,
+ ]);
+
+$ENV{"PGPASSWORD"} = 'secret2';
+test_access(
+ $node, 'test2', 2,
+ 'fails with unmapped role',
+ log_like => [
+ qr/connection authenticated: identity="uid=test2,dc=example,dc=net" method=ldap/,
+ qr/no match in usermap "mymap" for user "test2" authenticated as "test2"/,
+ ]);
+
+$ENV{"PGAUTHUSER"} = 'test2';
+test_access(
+ $node, 'test1', 0,
+ 'succeeds with different PGAUTHUSER',
+ log_like => [
+ qr/connection authenticated: identity="uid=test2,dc=example,dc=net" method=ldap/,
+ qr/connection authorized: user=test1/,
+ ]);
+delete $ENV{"PGAUTHUSER"};
+
+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" map=mymap ldap_map_dn=1}
+);
+unlink($node->data_dir . '/pg_ident.conf');
+$node->append_conf('pg_ident.conf', qq{mymap "/,dc=example,dc=net\$" test1});
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access(
+ $node, 'test1', 0,
+ 'succeeds with full DN mapping',
+ log_like => [
+ qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/,
+ ]);
+
+$ENV{"PGPASSWORD"} = 'secret2';
+test_access(
+ $node, 'test2', 2,
+ 'fails with unmapped role for full DN mapping',
+ log_like => [
+ qr/connection authenticated: identity="uid=test2,dc=example,dc=net" method=ldap/,
+ qr/no match in usermap "mymap" for user "test2" authenticated as "uid=test2,dc=example,dc=net"/,
+ ]);
+
note "search+bind";
unlink($node->data_dir . '/pg_hba.conf');
@@ -231,6 +302,78 @@ test_access(
qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/
],);
+note "search+bind ident mapping";
+
+unlink($node->data_dir . '/pg_ident.conf');
+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" map=mymap}
+);
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access(
+ $node, 'test1', 2,
+ 'fails without mapping',
+ log_like => [
+ qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/,
+ qr/no match in usermap "mymap" for user "test1" authenticated as "test1"/,
+ ]);
+
+$node->append_conf('pg_ident.conf', qq{mymap /^ test1});
+$node->restart;
+
+test_access(
+ $node, 'test1', 0,
+ 'succeeds with mapping',
+ log_like => [
+ qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/,
+ ]);
+
+$ENV{"PGPASSWORD"} = 'secret2';
+test_access(
+ $node, 'test2', 2,
+ 'fails with unmapped role',
+ log_like => [
+ qr/connection authenticated: identity="uid=test2,dc=example,dc=net" method=ldap/,
+ qr/no match in usermap "mymap" for user "test2" authenticated as "test2"/,
+ ]);
+
+$ENV{"PGAUTHUSER"} = 'test2';
+test_access(
+ $node, 'test1', 0,
+ 'succeeds with different PGAUTHUSER',
+ log_like => [
+ qr/connection authenticated: identity="uid=test2,dc=example,dc=net" method=ldap/,
+ qr/connection authorized: user=test1/,
+ ]);
+delete $ENV{"PGAUTHUSER"};
+
+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" map=mymap ldap_map_dn=1}
+);
+unlink($node->data_dir . '/pg_ident.conf');
+$node->append_conf('pg_ident.conf', qq{mymap "/,dc=example,dc=net\$" test1});
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access(
+ $node, 'test1', 0,
+ 'succeeds with full DN mapping',
+ log_like => [
+ qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/,
+ ]);
+
+$ENV{"PGPASSWORD"} = 'secret2';
+test_access(
+ $node, 'test2', 2,
+ 'fails with unmapped role for full DN mapping',
+ log_like => [
+ qr/connection authenticated: identity="uid=test2,dc=example,dc=net" method=ldap/,
+ qr/no match in usermap "mymap" for user "test2" authenticated as "uid=test2,dc=example,dc=net"/,
+ ]);
+
note "multiple servers";
unlink($node->data_dir . '/pg_hba.conf');
--
2.25.1