aboutsummaryrefslogtreecommitdiff
path: root/src/backend/libpq/auth.c
diff options
context:
space:
mode:
authorMichael Paquier <michael@paquier.xyz>2021-04-07 10:16:39 +0900
committerMichael Paquier <michael@paquier.xyz>2021-04-07 10:16:39 +0900
commit9afffcb833d3c5e59a328a2af674fac7e7334fc1 (patch)
tree48b3aff83fefc902317e30802abd453d5228a906 /src/backend/libpq/auth.c
parent8ee9b662daa6d51b54d21ec274f22a218462ad2d (diff)
downloadpostgresql-9afffcb833d3c5e59a328a2af674fac7e7334fc1.tar.gz
postgresql-9afffcb833d3c5e59a328a2af674fac7e7334fc1.zip
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
Diffstat (limited to 'src/backend/libpq/auth.c')
-rw-r--r--src/backend/libpq/auth.c136
1 files changed, 129 insertions, 7 deletions
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 9dc28e19aaf..dee056b0d65 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -34,8 +34,10 @@
#include "libpq/scram.h"
#include "miscadmin.h"
#include "port/pg_bswap.h"
+#include "postmaster/postmaster.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
+#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
@@ -47,6 +49,7 @@ static void sendAuthRequest(Port *port, AuthRequest areq, const char *extradata,
int extralen);
static void auth_failed(Port *port, int status, char *logdetail);
static char *recv_password_packet(Port *port);
+static void set_authn_id(Port *port, const char *id);
/*----------------------------------------------------------------
@@ -338,6 +341,51 @@ auth_failed(Port *port, int status, char *logdetail)
/*
+ * Sets the authenticated identity for the current user. The provided string
+ * will be copied into the TopMemoryContext. The ID will be logged if
+ * log_connections is enabled.
+ *
+ * Auth methods should call this routine exactly once, as soon as the user is
+ * successfully authenticated, even if they have reasons to know that
+ * authorization will fail later.
+ *
+ * The provided string will be copied into TopMemoryContext, to match the
+ * lifetime of the Port, so it is safe to pass a string that is managed by an
+ * external library.
+ */
+static void
+set_authn_id(Port *port, const char *id)
+{
+ Assert(id);
+
+ if (port->authn_id)
+ {
+ /*
+ * An existing authn_id should never be overwritten; that means two
+ * authentication providers are fighting (or one is fighting itself).
+ * Don't leak any authn details to the client, but don't let the
+ * connection continue, either.
+ */
+ ereport(FATAL,
+ (errmsg("connection was re-authenticated"),
+ errdetail_log("previous ID: \"%s\"; new ID: \"%s\"",
+ port->authn_id, id)));
+ }
+
+ port->authn_id = MemoryContextStrdup(TopMemoryContext, id);
+
+ if (Log_connections)
+ {
+ ereport(LOG,
+ errmsg("connection authenticated: identity=\"%s\" method=%s "
+ "(%s:%d)",
+ port->authn_id, hba_authname(port), HbaFileName,
+ port->hba->linenumber));
+ }
+}
+
+
+/*
* Client authentication starts here. If there is an error, this
* function does not return and the backend process is terminated.
*/
@@ -757,6 +805,9 @@ CheckPasswordAuth(Port *port, char **logdetail)
pfree(shadow_pass);
pfree(passwd);
+ if (result == STATUS_OK)
+ set_authn_id(port, port->user_name);
+
return result;
}
@@ -816,6 +867,10 @@ CheckPWChallengeAuth(Port *port, char **logdetail)
Assert(auth_result != STATUS_OK);
return STATUS_ERROR;
}
+
+ if (auth_result == STATUS_OK)
+ set_authn_id(port, port->user_name);
+
return auth_result;
}
@@ -1174,8 +1229,13 @@ pg_GSS_checkauth(Port *port)
/*
* Copy the original name of the authenticated principal into our backend
* memory for display later.
+ *
+ * This is also our authenticated identity. Set it now, rather than
+ * waiting for the usermap check below, because authentication has already
+ * succeeded and we want the log file to reflect that.
*/
port->gss->princ = MemoryContextStrdup(TopMemoryContext, gbuf.value);
+ set_authn_id(port, gbuf.value);
/*
* Split the username at the realm separator
@@ -1285,6 +1345,7 @@ pg_SSPI_recvauth(Port *port)
DWORD domainnamesize = sizeof(domainname);
SID_NAME_USE accountnameuse;
HMODULE secur32;
+ char *authn_id;
QUERY_SECURITY_CONTEXT_TOKEN_FN _QuerySecurityContextToken;
@@ -1515,6 +1576,26 @@ pg_SSPI_recvauth(Port *port)
}
/*
+ * We have all of the information necessary to construct the authenticated
+ * identity. Set it now, rather than waiting for check_usermap below,
+ * because authentication has already succeeded and we want the log file
+ * to reflect that.
+ */
+ if (port->hba->compat_realm)
+ {
+ /* SAM-compatible format. */
+ authn_id = psprintf("%s\\%s", domainname, accountname);
+ }
+ else
+ {
+ /* Kerberos principal format. */
+ authn_id = psprintf("%s@%s", accountname, domainname);
+ }
+
+ set_authn_id(port, authn_id);
+ pfree(authn_id);
+
+ /*
* Compare realm/domain if requested. In SSPI, always compare case
* insensitive.
*/
@@ -1901,8 +1982,15 @@ ident_inet_done:
pg_freeaddrinfo_all(local_addr.addr.ss_family, la);
if (ident_return)
- /* Success! Check the usermap */
+ {
+ /*
+ * Success! Store the identity, then check the usermap. Note that
+ * setting the authenticated identity is done before checking the
+ * usermap, because at this point authentication has succeeded.
+ */
+ set_authn_id(port, ident_user);
return check_usermap(port->hba->usermap, port->user_name, ident_user, false);
+ }
return STATUS_ERROR;
}
@@ -1926,7 +2014,6 @@ auth_peer(hbaPort *port)
gid_t gid;
#ifndef WIN32
struct passwd *pw;
- char *peer_user;
int ret;
#endif
@@ -1958,12 +2045,14 @@ auth_peer(hbaPort *port)
return STATUS_ERROR;
}
- /* Make a copy of static getpw*() result area. */
- peer_user = pstrdup(pw->pw_name);
-
- ret = check_usermap(port->hba->usermap, port->user_name, peer_user, false);
+ /*
+ * Make a copy of static getpw*() result area; this is our authenticated
+ * identity. Set it before calling check_usermap, because authentication
+ * has already succeeded and we want the log file to reflect that.
+ */
+ set_authn_id(port, pw->pw_name);
- pfree(peer_user);
+ ret = check_usermap(port->hba->usermap, port->user_name, port->authn_id, false);
return ret;
#else
@@ -2220,6 +2309,9 @@ CheckPAMAuth(Port *port, const char *user, const char *password)
pam_passwd = NULL; /* Unset pam_passwd */
+ if (retval == PAM_SUCCESS)
+ set_authn_id(port, user);
+
return (retval == PAM_SUCCESS ? STATUS_OK : STATUS_ERROR);
}
#endif /* USE_PAM */
@@ -2255,6 +2347,7 @@ CheckBSDAuth(Port *port, char *user)
if (!retval)
return STATUS_ERROR;
+ set_authn_id(port, user);
return STATUS_OK;
}
#endif /* USE_BSD_AUTH */
@@ -2761,6 +2854,9 @@ CheckLDAPAuth(Port *port)
return STATUS_ERROR;
}
+ /* Save the original bind DN as the authenticated identity. */
+ set_authn_id(port, fulluser);
+
ldap_unbind(ldap);
pfree(passwd);
pfree(fulluser);
@@ -2824,6 +2920,30 @@ CheckCertAuth(Port *port)
return STATUS_ERROR;
}
+ if (port->hba->auth_method == uaCert)
+ {
+ /*
+ * For cert auth, the client's Subject DN is always our authenticated
+ * identity, even if we're only using its CN for authorization. Set
+ * it now, rather than waiting for check_usermap() below, because
+ * authentication has already succeeded and we want the log file to
+ * reflect that.
+ */
+ if (!port->peer_dn)
+ {
+ /*
+ * This should not happen as both peer_dn and peer_cn should be
+ * set in this context.
+ */
+ ereport(LOG,
+ (errmsg("certificate authentication failed for user \"%s\": unable to retrieve subject DN",
+ port->user_name)));
+ return STATUS_ERROR;
+ }
+
+ set_authn_id(port, port->peer_dn);
+ }
+
/* Just pass the certificate cn/dn to the usermap check */
status_check_usermap = check_usermap(port->hba->usermap, port->user_name, peer_username, false);
if (status_check_usermap != STATUS_OK)
@@ -2995,6 +3115,8 @@ CheckRADIUSAuth(Port *port)
*/
if (ret == STATUS_OK)
{
+ set_authn_id(port, port->user_name);
+
pfree(passwd);
return STATUS_OK;
}