Skip to content

Commit 9afffcb

Browse files
committed
Add some information about authenticated identity via log_connections
The "authenticated identity" is the string used by an authentication method to identify a particular user. In many common cases, this is the same as the PostgreSQL username, but for some third-party authentication methods, the identifier in use may be shortened or otherwise translated (e.g. through pg_ident user mappings) before the server stores it. To help administrators see who has actually interacted with the system, this commit adds the capability to store the original identity when authentication succeeds within the backend's Port, and generates a log entry when log_connections is enabled. The log entries generated look something like this (where a local user named "foouser" is connecting to the database as the database user called "admin"): LOG: connection received: host=[local] LOG: connection authenticated: identity="foouser" method=peer (/data/pg_hba.conf:88) LOG: connection authorized: user=admin database=postgres application_name=psql Port->authn_id is set according to the authentication method: bsd: the PostgreSQL username (aka the local username) cert: the client's Subject DN gss: the user principal ident: the remote username ldap: the final bind DN pam: the PostgreSQL username (aka PAM username) password (and all pw-challenge methods): the PostgreSQL username peer: the peer's pw_name radius: the PostgreSQL username (aka the RADIUS username) sspi: either the down-level (SAM-compatible) logon name, if compat_realm=1, or the User Principal Name if compat_realm=0 The trust auth method does not set an authenticated identity. Neither does clientcert=verify-full. Port->authn_id could be used for other purposes, like a superuser-only extra column in pg_stat_activity, but this is left as future work. PostgresNode::connect_{ok,fails}() have been modified to let tests check the backend log files for required or prohibited patterns, using the new log_like and log_unlike parameters. This uses a method based on a truncation of the existing server log file, like issues_sql_like(). Tests are added to the ldap, kerberos, authentication and SSL test suites. Author: Jacob Champion Reviewed-by: Stephen Frost, Magnus Hagander, Tom Lane, Michael Paquier Discussion: https://postgr.es/m/c55788dd1773c521c862e8e0dddb367df51222be.camel@vmware.com
1 parent 8ee9b66 commit 9afffcb

File tree

11 files changed

+416
-74
lines changed

11 files changed

+416
-74
lines changed

doc/src/sgml/config.sgml

+2-1
Original file line numberDiff line numberDiff line change
@@ -6755,7 +6755,8 @@ local0.* /var/log/postgresql
67556755
<listitem>
67566756
<para>
67576757
Causes each attempted connection to the server to be logged,
6758-
as well as successful completion of client authentication.
6758+
as well as successful completion of both client authentication (if
6759+
necessary) and authorization.
67596760
Only superusers can change this parameter at session start,
67606761
and it cannot be changed at all within a session.
67616762
The default is <literal>off</literal>.

src/backend/libpq/auth.c

+129-7
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@
3434
#include "libpq/scram.h"
3535
#include "miscadmin.h"
3636
#include "port/pg_bswap.h"
37+
#include "postmaster/postmaster.h"
3738
#include "replication/walsender.h"
3839
#include "storage/ipc.h"
40+
#include "utils/guc.h"
3941
#include "utils/memutils.h"
4042
#include "utils/timestamp.h"
4143

@@ -47,6 +49,7 @@ static void sendAuthRequest(Port *port, AuthRequest areq, const char *extradata,
4749
int extralen);
4850
static void auth_failed(Port *port, int status, char *logdetail);
4951
static char *recv_password_packet(Port *port);
52+
static void set_authn_id(Port *port, const char *id);
5053

5154

5255
/*----------------------------------------------------------------
@@ -337,6 +340,51 @@ auth_failed(Port *port, int status, char *logdetail)
337340
}
338341

339342

343+
/*
344+
* Sets the authenticated identity for the current user. The provided string
345+
* will be copied into the TopMemoryContext. The ID will be logged if
346+
* log_connections is enabled.
347+
*
348+
* Auth methods should call this routine exactly once, as soon as the user is
349+
* successfully authenticated, even if they have reasons to know that
350+
* authorization will fail later.
351+
*
352+
* The provided string will be copied into TopMemoryContext, to match the
353+
* lifetime of the Port, so it is safe to pass a string that is managed by an
354+
* external library.
355+
*/
356+
static void
357+
set_authn_id(Port *port, const char *id)
358+
{
359+
Assert(id);
360+
361+
if (port->authn_id)
362+
{
363+
/*
364+
* An existing authn_id should never be overwritten; that means two
365+
* authentication providers are fighting (or one is fighting itself).
366+
* Don't leak any authn details to the client, but don't let the
367+
* connection continue, either.
368+
*/
369+
ereport(FATAL,
370+
(errmsg("connection was re-authenticated"),
371+
errdetail_log("previous ID: \"%s\"; new ID: \"%s\"",
372+
port->authn_id, id)));
373+
}
374+
375+
port->authn_id = MemoryContextStrdup(TopMemoryContext, id);
376+
377+
if (Log_connections)
378+
{
379+
ereport(LOG,
380+
errmsg("connection authenticated: identity=\"%s\" method=%s "
381+
"(%s:%d)",
382+
port->authn_id, hba_authname(port), HbaFileName,
383+
port->hba->linenumber));
384+
}
385+
}
386+
387+
340388
/*
341389
* Client authentication starts here. If there is an error, this
342390
* function does not return and the backend process is terminated.
@@ -757,6 +805,9 @@ CheckPasswordAuth(Port *port, char **logdetail)
757805
pfree(shadow_pass);
758806
pfree(passwd);
759807

808+
if (result == STATUS_OK)
809+
set_authn_id(port, port->user_name);
810+
760811
return result;
761812
}
762813

@@ -816,6 +867,10 @@ CheckPWChallengeAuth(Port *port, char **logdetail)
816867
Assert(auth_result != STATUS_OK);
817868
return STATUS_ERROR;
818869
}
870+
871+
if (auth_result == STATUS_OK)
872+
set_authn_id(port, port->user_name);
873+
819874
return auth_result;
820875
}
821876

@@ -1174,8 +1229,13 @@ pg_GSS_checkauth(Port *port)
11741229
/*
11751230
* Copy the original name of the authenticated principal into our backend
11761231
* memory for display later.
1232+
*
1233+
* This is also our authenticated identity. Set it now, rather than
1234+
* waiting for the usermap check below, because authentication has already
1235+
* succeeded and we want the log file to reflect that.
11771236
*/
11781237
port->gss->princ = MemoryContextStrdup(TopMemoryContext, gbuf.value);
1238+
set_authn_id(port, gbuf.value);
11791239

11801240
/*
11811241
* Split the username at the realm separator
@@ -1285,6 +1345,7 @@ pg_SSPI_recvauth(Port *port)
12851345
DWORD domainnamesize = sizeof(domainname);
12861346
SID_NAME_USE accountnameuse;
12871347
HMODULE secur32;
1348+
char *authn_id;
12881349

12891350
QUERY_SECURITY_CONTEXT_TOKEN_FN _QuerySecurityContextToken;
12901351

@@ -1514,6 +1575,26 @@ pg_SSPI_recvauth(Port *port)
15141575
return status;
15151576
}
15161577

1578+
/*
1579+
* We have all of the information necessary to construct the authenticated
1580+
* identity. Set it now, rather than waiting for check_usermap below,
1581+
* because authentication has already succeeded and we want the log file
1582+
* to reflect that.
1583+
*/
1584+
if (port->hba->compat_realm)
1585+
{
1586+
/* SAM-compatible format. */
1587+
authn_id = psprintf("%s\\%s", domainname, accountname);
1588+
}
1589+
else
1590+
{
1591+
/* Kerberos principal format. */
1592+
authn_id = psprintf("%s@%s", accountname, domainname);
1593+
}
1594+
1595+
set_authn_id(port, authn_id);
1596+
pfree(authn_id);
1597+
15171598
/*
15181599
* Compare realm/domain if requested. In SSPI, always compare case
15191600
* insensitive.
@@ -1901,8 +1982,15 @@ ident_inet(hbaPort *port)
19011982
pg_freeaddrinfo_all(local_addr.addr.ss_family, la);
19021983

19031984
if (ident_return)
1904-
/* Success! Check the usermap */
1985+
{
1986+
/*
1987+
* Success! Store the identity, then check the usermap. Note that
1988+
* setting the authenticated identity is done before checking the
1989+
* usermap, because at this point authentication has succeeded.
1990+
*/
1991+
set_authn_id(port, ident_user);
19051992
return check_usermap(port->hba->usermap, port->user_name, ident_user, false);
1993+
}
19061994
return STATUS_ERROR;
19071995
}
19081996

@@ -1926,7 +2014,6 @@ auth_peer(hbaPort *port)
19262014
gid_t gid;
19272015
#ifndef WIN32
19282016
struct passwd *pw;
1929-
char *peer_user;
19302017
int ret;
19312018
#endif
19322019

@@ -1958,12 +2045,14 @@ auth_peer(hbaPort *port)
19582045
return STATUS_ERROR;
19592046
}
19602047

1961-
/* Make a copy of static getpw*() result area. */
1962-
peer_user = pstrdup(pw->pw_name);
1963-
1964-
ret = check_usermap(port->hba->usermap, port->user_name, peer_user, false);
2048+
/*
2049+
* Make a copy of static getpw*() result area; this is our authenticated
2050+
* identity. Set it before calling check_usermap, because authentication
2051+
* has already succeeded and we want the log file to reflect that.
2052+
*/
2053+
set_authn_id(port, pw->pw_name);
19652054

1966-
pfree(peer_user);
2055+
ret = check_usermap(port->hba->usermap, port->user_name, port->authn_id, false);
19672056

19682057
return ret;
19692058
#else
@@ -2220,6 +2309,9 @@ CheckPAMAuth(Port *port, const char *user, const char *password)
22202309

22212310
pam_passwd = NULL; /* Unset pam_passwd */
22222311

2312+
if (retval == PAM_SUCCESS)
2313+
set_authn_id(port, user);
2314+
22232315
return (retval == PAM_SUCCESS ? STATUS_OK : STATUS_ERROR);
22242316
}
22252317
#endif /* USE_PAM */
@@ -2255,6 +2347,7 @@ CheckBSDAuth(Port *port, char *user)
22552347
if (!retval)
22562348
return STATUS_ERROR;
22572349

2350+
set_authn_id(port, user);
22582351
return STATUS_OK;
22592352
}
22602353
#endif /* USE_BSD_AUTH */
@@ -2761,6 +2854,9 @@ CheckLDAPAuth(Port *port)
27612854
return STATUS_ERROR;
27622855
}
27632856

2857+
/* Save the original bind DN as the authenticated identity. */
2858+
set_authn_id(port, fulluser);
2859+
27642860
ldap_unbind(ldap);
27652861
pfree(passwd);
27662862
pfree(fulluser);
@@ -2824,6 +2920,30 @@ CheckCertAuth(Port *port)
28242920
return STATUS_ERROR;
28252921
}
28262922

2923+
if (port->hba->auth_method == uaCert)
2924+
{
2925+
/*
2926+
* For cert auth, the client's Subject DN is always our authenticated
2927+
* identity, even if we're only using its CN for authorization. Set
2928+
* it now, rather than waiting for check_usermap() below, because
2929+
* authentication has already succeeded and we want the log file to
2930+
* reflect that.
2931+
*/
2932+
if (!port->peer_dn)
2933+
{
2934+
/*
2935+
* This should not happen as both peer_dn and peer_cn should be
2936+
* set in this context.
2937+
*/
2938+
ereport(LOG,
2939+
(errmsg("certificate authentication failed for user \"%s\": unable to retrieve subject DN",
2940+
port->user_name)));
2941+
return STATUS_ERROR;
2942+
}
2943+
2944+
set_authn_id(port, port->peer_dn);
2945+
}
2946+
28272947
/* Just pass the certificate cn/dn to the usermap check */
28282948
status_check_usermap = check_usermap(port->hba->usermap, port->user_name, peer_username, false);
28292949
if (status_check_usermap != STATUS_OK)
@@ -2995,6 +3115,8 @@ CheckRADIUSAuth(Port *port)
29953115
*/
29963116
if (ret == STATUS_OK)
29973117
{
3118+
set_authn_id(port, port->user_name);
3119+
29983120
pfree(passwd);
29993121
return STATUS_OK;
30003122
}

src/backend/libpq/hba.c

+24
Original file line numberDiff line numberDiff line change
@@ -3141,3 +3141,27 @@ hba_getauthmethod(hbaPort *port)
31413141
{
31423142
check_hba(port);
31433143
}
3144+
3145+
3146+
/*
3147+
* Return the name of the auth method in use ("gss", "md5", "trust", etc.).
3148+
*
3149+
* The return value is statically allocated (see the UserAuthName array) and
3150+
* should not be freed.
3151+
*/
3152+
const char *
3153+
hba_authname(hbaPort *port)
3154+
{
3155+
UserAuth auth_method;
3156+
3157+
Assert(port->hba);
3158+
auth_method = port->hba->auth_method;
3159+
3160+
if (auth_method < 0 || USER_AUTH_LAST < auth_method)
3161+
{
3162+
/* Should never happen. */
3163+
elog(FATAL, "port has out-of-bounds UserAuth: %d", auth_method);
3164+
}
3165+
3166+
return UserAuthName[auth_method];
3167+
}

src/include/libpq/hba.h

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ typedef struct Port hbaPort;
137137

138138
extern bool load_hba(void);
139139
extern bool load_ident(void);
140+
extern const char *hba_authname(hbaPort *port);
140141
extern void hba_getauthmethod(hbaPort *port);
141142
extern int check_usermap(const char *usermap_name,
142143
const char *pg_role, const char *auth_user,

src/include/libpq/libpq-be.h

+13
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,19 @@ typedef struct Port
159159
*/
160160
HbaLine *hba;
161161

162+
/*
163+
* Authenticated identity. The meaning of this identifier is dependent on
164+
* hba->auth_method; it is the identity (if any) that the user presented
165+
* during the authentication cycle, before they were assigned a database
166+
* role. (It is effectively the "SYSTEM-USERNAME" of a pg_ident usermap
167+
* -- though the exact string in use may be different, depending on pg_hba
168+
* options.)
169+
*
170+
* authn_id is NULL if the user has not actually been authenticated, for
171+
* example if the "trust" auth method is in use.
172+
*/
173+
const char *authn_id;
174+
162175
/*
163176
* TCP keepalive and user timeout settings.
164177
*

0 commit comments

Comments
 (0)