Fix assorted issues in backend's GSSAPI encryption support.

Unrecoverable errors detected by GSSAPI encryption can't just be
reported with elog(ERROR) or elog(FATAL), because attempting to
send the error report to the client is likely to lead to infinite
recursion or loss of protocol sync.  Instead make this code do what
the SSL encryption code has long done, which is to just report any
such failure to the server log (with elevel COMMERROR), then pretend
we've lost the connection by returning errno = ECONNRESET.

Along the way, fix confusion about whether message translation is done
by pg_GSS_error() or its callers (the latter should do it), and make
the backend version of that function work more like the frontend
version.

Avoid allocating the port->gss struct until it's needed; we surely
don't need to allocate it in the postmaster.

Improve logging of "connection authorized" messages with GSS enabled.
(As part of this, I back-patched the code changes from dc11f31a1.)

Make BackendStatusShmemSize() account for the GSS-related space that
will be allocated by CreateSharedBackendStatus().  This omission
could possibly cause out-of-shared-memory problems with very high
max_connections settings.

Remove arbitrary, pointless restriction that only GSS authentication
can be used on a GSS-encrypted connection.

Improve documentation; notably, document the fact that libpq now
prefers GSS encryption over SSL encryption if both are possible.

Per report from Mikael Gustavsson.  Back-patch to v12 where
this code was introduced.

Discussion: https://postgr.es/m/e5b0b6ed05764324a2f3fe7acfc766d5@smhi.se
This commit is contained in:
Tom Lane 2020-12-28 17:44:17 -05:00
parent ff6ce9a3a6
commit 622ae4621e
15 changed files with 300 additions and 210 deletions

View File

@ -198,14 +198,6 @@ hostnogssenc <replaceable>database</replaceable> <replaceable>user</replaceabl
the <literal>hostgssenc</literal> record is ignored except for logging the <literal>hostgssenc</literal> record is ignored except for logging
a warning that it cannot match any connections. a warning that it cannot match any connections.
</para> </para>
<para>
Note that the only supported
<link linkend="auth-methods">authentication methods</link> for use
with <acronym>GSSAPI</acronym> encryption
are <literal>gss</literal>, <literal>reject</literal>,
and <literal>trust</literal>.
</para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -1201,14 +1193,13 @@ omicron bryanh guest1
<productname>GSSAPI</productname> is an industry-standard protocol <productname>GSSAPI</productname> is an industry-standard protocol
for secure authentication defined in for secure authentication defined in
<ulink url="https://tools.ietf.org/html/rfc2743">RFC 2743</ulink>. <ulink url="https://tools.ietf.org/html/rfc2743">RFC 2743</ulink>.
<productname>PostgreSQL</productname> <productname>PostgreSQL</productname>
supports <productname>GSSAPI</productname> for use as either an encrypted, supports <productname>GSSAPI</productname> for authentication,
authenticated layer, or for authentication only. communications encryption, or both.
<productname>GSSAPI</productname> provides automatic authentication <productname>GSSAPI</productname> provides automatic authentication
(single sign-on) for systems that support it. The authentication itself is (single sign-on) for systems that support it. The authentication itself is
secure. If <productname>GSSAPI</productname> encryption secure. If <productname>GSSAPI</productname> encryption
(see <literal>hostgssenc</literal>) or <acronym>SSL</acronym> encryption are or <acronym>SSL</acronym> encryption is
used, the data sent along the database connection will be encrypted; used, the data sent along the database connection will be encrypted;
otherwise, it will not. otherwise, it will not.
</para> </para>
@ -1220,37 +1211,46 @@ omicron bryanh guest1
<para> <para>
When <productname>GSSAPI</productname> uses When <productname>GSSAPI</productname> uses
<productname>Kerberos</productname>, it uses a standard principal <productname>Kerberos</productname>, it uses a standard service
in the format principal (authentication identity) name in the format
<literal><replaceable>servicename</replaceable>/<replaceable>hostname</replaceable>@<replaceable>realm</replaceable></literal>. <literal><replaceable>servicename</replaceable>/<replaceable>hostname</replaceable>@<replaceable>realm</replaceable></literal>.
The PostgreSQL server will accept any principal that is included in the keytab used by The principal name used by a particular installation is not encoded in
the server, but care needs to be taken to specify the correct principal details when the <productname>PostgreSQL</productname> server in any way; rather it
making the connection from the client using the <literal>krbsrvname</literal> connection parameter. (See is specified in the <firstterm>keytab</firstterm> file that the server
also <xref linkend="libpq-paramkeywords"/>.) The installation default can be reads to determine its identity. If multiple principals are listed in
changed from the default <literal>postgres</literal> at build time using the keytab file, the server will accept any one of them.
<literal>./configure --with-krb-srvnam=</literal><replaceable>whatever</replaceable>. The server's realm name is the preferred realm specified in the Kerberos
In most environments, configuration file(s) accessible to the server.
this parameter never needs to be changed.
Some Kerberos implementations might require a different service name,
such as Microsoft Active Directory which requires the service name
to be in upper case (<literal>POSTGRES</literal>).
</para>
<para>
<replaceable>hostname</replaceable> is the fully qualified host name of the
server machine. The service principal's realm is the preferred realm
of the server machine.
</para> </para>
<para> <para>
Client principals can be mapped to different <productname>PostgreSQL</productname> When connecting, the client must know the principal name of the server
database user names with <filename>pg_ident.conf</filename>. For example, it intends to connect to. The <replaceable>servicename</replaceable>
part of the principal is ordinarily <literal>postgres</literal>,
but another value can be selected via <application>libpq</application>'s
<xref linkend="libpq-connect-krbsrvname"/> connection parameter.
The <replaceable>hostname</replaceable> part is the fully qualified
host name that <application>libpq</application> is told to connect to.
The realm name is the preferred realm specified in the Kerberos
configuration file(s) accessible to the client.
</para>
<para>
The client will also have a principal name for its own identity
(and it must have a valid ticket for this principal). To
use <productname>GSSAPI</productname> for authentication, the client
principal must be associated with
a <productname>PostgreSQL</productname> database user name.
The <filename>pg_ident.conf</filename> configuration file can be used
to map principals to user names; for example,
<literal>pgusername@realm</literal> could be mapped to just <literal>pgusername</literal>. <literal>pgusername@realm</literal> could be mapped to just <literal>pgusername</literal>.
Alternatively, you can use the full <literal>username@realm</literal> principal as Alternatively, you can use the full <literal>username@realm</literal> principal as
the role name in <productname>PostgreSQL</productname> without any mapping. the role name in <productname>PostgreSQL</productname> without any mapping.
</para> </para>
<para> <para>
<productname>PostgreSQL</productname> also supports a parameter to strip the realm from <productname>PostgreSQL</productname> also supports mapping
client principals to user names by just stripping the realm from
the principal. This method is supported for backwards compatibility and is the principal. This method is supported for backwards compatibility and is
strongly discouraged as it is then impossible to distinguish different users strongly discouraged as it is then impossible to distinguish different users
with the same user name but coming from different realms. To enable this, with the same user name but coming from different realms. To enable this,
@ -1264,39 +1264,34 @@ omicron bryanh guest1
</para> </para>
<para> <para>
Make sure that your server keytab file is readable (and preferably The location of the server's keytab file is specified by the <xref
only readable, not writable) by the <productname>PostgreSQL</productname>
server account. (See also <xref linkend="postgres-user"/>.) The location
of the key file is specified by the <xref
linkend="guc-krb-server-keyfile"/> configuration linkend="guc-krb-server-keyfile"/> configuration
parameter. The default is parameter. The default is
<filename>/usr/local/pgsql/etc/krb5.keytab</filename> (or whatever <filename>FILE:/usr/local/pgsql/etc/krb5.keytab</filename>
directory was specified as <varname>sysconfdir</varname> at build time). (where the directory part is whatever was specified
as <varname>sysconfdir</varname> at build time).
For security reasons, it is recommended to use a separate keytab For security reasons, it is recommended to use a separate keytab
just for the <productname>PostgreSQL</productname> server rather just for the <productname>PostgreSQL</productname> server rather
than opening up permissions on the system keytab file. than allowing the server to read the system keytab file.
Make sure that your server keytab file is readable (and preferably
only readable, not writable) by the <productname>PostgreSQL</productname>
server account. (See also <xref linkend="postgres-user"/>.)
</para> </para>
<para> <para>
The keytab file is generated by the Kerberos software; see the The keytab file is generated using the Kerberos software; see the
Kerberos documentation for details. The following example is Kerberos documentation for details. The following example shows
for MIT-compatible Kerberos 5 implementations: doing this using the <application>kadmin</application> tool of
MIT-compatible Kerberos 5 implementations:
<screen> <screen>
<prompt>kadmin% </prompt><userinput>ank -randkey postgres/server.my.domain.org</userinput> <prompt>kadmin% </prompt><userinput>addprinc -randkey postgres/server.my.domain.org</userinput>
<prompt>kadmin% </prompt><userinput>ktadd -k krb5.keytab postgres/server.my.domain.org</userinput> <prompt>kadmin% </prompt><userinput>ktadd -k krb5.keytab postgres/server.my.domain.org</userinput>
</screen> </screen>
</para> </para>
<para> <para>
When connecting to the database make sure you have a ticket for a The following authentication options are supported for
principal matching the requested database user name. For example, for the <productname>GSSAPI</productname> authentication method:
database user name <literal>fred</literal>, principal
<literal>fred@EXAMPLE.COM</literal> would be able to connect. To also allow
principal <literal>fred/users.example.com@EXAMPLE.COM</literal>, use a user name
map, as described in <xref linkend="auth-username-maps"/>.
</para>
<para>
The following configuration options are supported for <productname>GSSAPI</productname>:
<variablelist> <variablelist>
<varlistentry> <varlistentry>
<term><literal>include_realm</literal></term> <term><literal>include_realm</literal></term>
@ -1319,7 +1314,7 @@ omicron bryanh guest1
<term><literal>map</literal></term> <term><literal>map</literal></term>
<listitem> <listitem>
<para> <para>
Allows for mapping between system and database user names. See Allows mapping from client principals to database user names. See
<xref linkend="auth-username-maps"/> for details. For a GSSAPI/Kerberos <xref linkend="auth-username-maps"/> for details. For a GSSAPI/Kerberos
principal, such as <literal>username@EXAMPLE.COM</literal> (or, less principal, such as <literal>username@EXAMPLE.COM</literal> (or, less
commonly, <literal>username/hostbased@EXAMPLE.COM</literal>), the commonly, <literal>username/hostbased@EXAMPLE.COM</literal>), the
@ -1346,6 +1341,15 @@ omicron bryanh guest1
</varlistentry> </varlistentry>
</variablelist> </variablelist>
</para> </para>
<para>
In addition to these settings, which can be different for
different <filename>pg_hba.conf</filename> entries, there is the
server-wide <xref linkend="guc-krb-caseins-users"/> configuration
parameter. If that is set to true, client principals are matched to
user map entries case-insensitively. <literal>krb_realm</literal>, if
set, is also matched case-insensitively.
</para>
</sect1> </sect1>
<sect1 id="sspi-auth"> <sect1 id="sspi-auth">

View File

@ -1581,6 +1581,16 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
connection.<indexterm><primary>SSL</primary><secondary connection.<indexterm><primary>SSL</primary><secondary
sortas="libpq">with libpq</secondary></indexterm> sortas="libpq">with libpq</secondary></indexterm>
</para> </para>
<para>
Note that if <acronym>GSSAPI</acronym> encryption is possible,
that will be used in preference to <acronym>SSL</acronym>
encryption, regardless of the value of <literal>sslmode</literal>.
To force use of <acronym>SSL</acronym> encryption in an
environment that has working <acronym>GSSAPI</acronym>
infrastructure (such as a Kerberos server), also
set <literal>gssencmode</literal> to <literal>disable</literal>.
</para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -1784,6 +1794,15 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
This must match the service name specified in the server This must match the service name specified in the server
configuration for Kerberos authentication to succeed. (See also configuration for Kerberos authentication to succeed. (See also
<xref linkend="gssapi-auth"/>.) <xref linkend="gssapi-auth"/>.)
The default value is normally <literal>postgres</literal>,
but that can be changed when
building <productname>PostgreSQL</productname> via
the <option>--with-krb-srvnam</option> option
of <application>configure</application>.
In most environments, this parameter never needs to be changed.
Some Kerberos implementations might require a different service name,
such as Microsoft Active Directory which requires the service name
to be in upper case (<literal>POSTGRES</literal>).
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>

View File

@ -1454,6 +1454,10 @@ SELCT 1/0;<!-- this typo is intentional -->
<acronym>SSL</acronym>-encrypted. To continue after <acronym>SSL</acronym>-encrypted. To continue after
<literal>N</literal>, send the usual StartupMessage and proceed without <literal>N</literal>, send the usual StartupMessage and proceed without
encryption. encryption.
(Alternatively, it is permissible to issue a GSSENCRequest message
after an <literal>N</literal> response to try to
use <acronym>GSSAPI</acronym> encryption instead
of <acronym>SSL</acronym>.)
</para> </para>
<para> <para>
@ -1509,7 +1513,34 @@ SELCT 1/0;<!-- this typo is intentional -->
result from the server, until it returns no output. When sending the result from the server, until it returns no output. When sending the
results of <function>gss_init_sec_context()</function> to the server, results of <function>gss_init_sec_context()</function> to the server,
prepend the length of the message as a four byte integer in network byte prepend the length of the message as a four byte integer in network byte
order. If this is successful, then use <function>gss_wrap()</function> to order.
To continue after
<literal>N</literal>, send the usual StartupMessage and proceed without
encryption.
(Alternatively, it is permissible to issue an SSLRequest message
after an <literal>N</literal> response to try to
use <acronym>SSL</acronym> encryption instead
of <acronym>GSSAPI</acronym>.)
</para>
<para>
The frontend should also be prepared to handle an ErrorMessage
response to GSSENCRequest from the server. This would only occur if
the server predates the addition of <acronym>GSSAPI</acronym> encryption
support to <productname>PostgreSQL</productname>. In this case the
connection must be closed, but the frontend might choose to open a fresh
connection and proceed without requesting <acronym>GSSAPI</acronym>
encryption.
</para>
<para>
An initial GSSENCRequest can also be used in a connection that is being
opened to send a CancelRequest message.
</para>
<para>
Once <acronym>GSSAPI</acronym> encryption has been successfully
established, use <function>gss_wrap()</function> to
encrypt the usual StartupMessage and all subsequent data, prepending the encrypt the usual StartupMessage and all subsequent data, prepending the
length of the result from <function>gss_wrap()</function> as a four byte length of the result from <function>gss_wrap()</function> as a four byte
integer in network byte order to the actual encrypted payload. Note that integer in network byte order to the actual encrypted payload. Note that
@ -1520,26 +1551,7 @@ SELCT 1/0;<!-- this typo is intentional -->
<function>gss_wrap()</function> calls. Typical segments are 8kB of <function>gss_wrap()</function> calls. Typical segments are 8kB of
unencrypted data, resulting in encrypted packets of slightly larger than 8kB unencrypted data, resulting in encrypted packets of slightly larger than 8kB
but well within the 16kB maximum. The server can be expected to not send but well within the 16kB maximum. The server can be expected to not send
encrypted packets of larger than 16kB to the client. To continue after encrypted packets of larger than 16kB to the client.
<literal>N</literal>, send the usual StartupMessage and proceed without
encryption.
</para>
<para>
The frontend should also be prepared to handle an ErrorMessage
response to GSSENCRequest from the server. This would only occur if
the server predates the addition of <acronym>GSSAPI</acronym> encryption
support to <productname>PostgreSQL</productname>. In this case the
connection must be closed, but the frontend might choose to open a fresh
connection and proceed without requesting <acronym>GSSAPI</acronym>
encryption. Given the length limits specified above, the ErrorMessage can
not be confused with a proper response from the server with an appropriate
length.
</para>
<para>
An initial GSSENCRequest can also be used in a connection that is being
opened to send a CancelRequest message.
</para> </para>
<para> <para>

View File

@ -2563,7 +2563,7 @@ openssl x509 -req -in server.csr -text -days 365 \
<productname>PostgreSQL</productname> also has native support for <productname>PostgreSQL</productname> also has native support for
using <acronym>GSSAPI</acronym> to encrypt client/server communications for using <acronym>GSSAPI</acronym> to encrypt client/server communications for
increased security. Support requires that a <acronym>GSSAPI</acronym> increased security. Support requires that a <acronym>GSSAPI</acronym>
implementation (such as MIT krb5) is installed on both client and server implementation (such as MIT Kerberos) is installed on both client and server
systems, and that support in <productname>PostgreSQL</productname> is systems, and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>). enabled at build time (see <xref linkend="installation"/>).
</para> </para>
@ -2582,6 +2582,16 @@ openssl x509 -req -in server.csr -text -days 365 \
some or all connections. some or all connections.
</para> </para>
<para>
When using <acronym>GSSAPI</acronym> for encryption, it is common to
use <acronym>GSSAPI</acronym> for authentication as well, since the
underlying mechanism will determine both client and server identities
(according to the <acronym>GSSAPI</acronym> implementation) in any
case. But this is not required;
another <productname>PostgreSQL</productname> authentication method
can be chosen to perform additional verification.
</para>
<para> <para>
Other than configuration of the negotiation Other than configuration of the negotiation
behavior, <acronym>GSSAPI</acronym> encryption requires no setup beyond behavior, <acronym>GSSAPI</acronym> encryption requires no setup beyond

View File

@ -382,17 +382,6 @@ ClientAuthentication(Port *port)
errmsg("connection requires a valid client certificate"))); errmsg("connection requires a valid client certificate")));
} }
#ifdef ENABLE_GSS
if (port->gss->enc && port->hba->auth_method != uaReject &&
port->hba->auth_method != uaImplicitReject &&
port->hba->auth_method != uaTrust &&
port->hba->auth_method != uaGSS)
{
ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg("GSSAPI encryption can only be used with gss, trust, or reject authentication methods")));
}
#endif
/* /*
* Now proceed to do the actual authentication check * Now proceed to do the actual authentication check
*/ */
@ -533,7 +522,17 @@ ClientAuthentication(Port *port)
case uaGSS: case uaGSS:
#ifdef ENABLE_GSS #ifdef ENABLE_GSS
/* We might or might not have the gss workspace already */
if (port->gss == NULL)
port->gss = (pg_gssinfo *)
MemoryContextAllocZero(TopMemoryContext,
sizeof(pg_gssinfo));
port->gss->auth = true; port->gss->auth = true;
/*
* If GSS state was set up while enabling encryption, we can just
* check the client's principal. Otherwise, ask for it.
*/
if (port->gss->enc) if (port->gss->enc)
status = pg_GSS_checkauth(port); status = pg_GSS_checkauth(port);
else else
@ -548,6 +547,10 @@ ClientAuthentication(Port *port)
case uaSSPI: case uaSSPI:
#ifdef ENABLE_SSPI #ifdef ENABLE_SSPI
if (port->gss == NULL)
port->gss = (pg_gssinfo *)
MemoryContextAllocZero(TopMemoryContext,
sizeof(pg_gssinfo));
sendAuthRequest(port, AUTH_REQ_SSPI, NULL, 0); sendAuthRequest(port, AUTH_REQ_SSPI, NULL, 0);
status = pg_SSPI_recvauth(port); status = pg_SSPI_recvauth(port);
#else #else
@ -1185,9 +1188,9 @@ pg_GSS_recvauth(Port *port)
if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED)
{ {
gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER); gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER);
pg_GSS_error(ERROR, pg_GSS_error(_("accepting GSS security context failed"),
_("accepting GSS security context failed"),
maj_stat, min_stat); maj_stat, min_stat);
return STATUS_ERROR;
} }
if (maj_stat == GSS_S_CONTINUE_NEEDED) if (maj_stat == GSS_S_CONTINUE_NEEDED)
@ -1224,9 +1227,11 @@ pg_GSS_checkauth(Port *port)
*/ */
maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL); maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL);
if (maj_stat != GSS_S_COMPLETE) if (maj_stat != GSS_S_COMPLETE)
pg_GSS_error(ERROR, {
_("retrieving GSS user name failed"), pg_GSS_error(_("retrieving GSS user name failed"),
maj_stat, min_stat); maj_stat, min_stat);
return STATUS_ERROR;
}
/* /*
* Copy the original name of the authenticated principal into our backend * Copy the original name of the authenticated principal into our backend

View File

@ -17,8 +17,9 @@
#include "libpq/be-gssapi-common.h" #include "libpq/be-gssapi-common.h"
/* /*
* Helper function for getting all strings of a GSSAPI error (of specified * Fetch all errors of a specific type and append to "s" (buffer of size len).
* stat). Call once for GSS_CODE and once for MECH_CODE. * If we obtain more than one string, separate them with spaces.
* Call once for GSS_CODE and once for MECH_CODE.
*/ */
static void static void
pg_GSS_error_int(char *s, size_t len, OM_uint32 stat, int type) pg_GSS_error_int(char *s, size_t len, OM_uint32 stat, int type)
@ -28,31 +29,49 @@ pg_GSS_error_int(char *s, size_t len, OM_uint32 stat, int type)
OM_uint32 lmin_s, OM_uint32 lmin_s,
msg_ctx = 0; msg_ctx = 0;
gmsg.value = NULL; s[0] = '\0'; /* just in case gss_display_status fails */
gmsg.length = 0;
do do
{ {
gss_display_status(&lmin_s, stat, type, if (gss_display_status(&lmin_s, stat, type, GSS_C_NO_OID,
GSS_C_NO_OID, &msg_ctx, &gmsg); &msg_ctx, &gmsg) != GSS_S_COMPLETE)
strlcpy(s + i, gmsg.value, len - i); break;
if (i > 0)
{
if (i < len)
s[i] = ' ';
i++;
}
if (i < len)
strlcpy(s + i, gmsg.value, len - i);
i += gmsg.length; i += gmsg.length;
gss_release_buffer(&lmin_s, &gmsg); gss_release_buffer(&lmin_s, &gmsg);
} }
while (msg_ctx && i < len); while (msg_ctx);
if (msg_ctx || i == len) if (i >= len)
ereport(WARNING, {
(errmsg_internal("incomplete GSS error report"))); elog(COMMERROR, "incomplete GSS error report");
s[len - 1] = '\0'; /* ensure string is nul-terminated */
}
} }
/* /*
* Fetch and report all error messages from GSSAPI. To avoid allocation, * Report the GSSAPI error described by maj_stat/min_stat.
* total error size is capped (at 128 bytes for each of major and minor). No *
* known mechanisms will produce error messages beyond this cap. * errmsg should be an already-translated primary error message.
* The GSSAPI info is appended as errdetail.
*
* The error is always reported with elevel COMMERROR; we daren't try to
* send it to the client, as that'd likely lead to infinite recursion
* when elog.c tries to write to the client.
*
* To avoid memory allocation, total error size is capped (at 128 bytes for
* each of major and minor). No known mechanisms will produce error messages
* beyond this cap.
*/ */
void void
pg_GSS_error(int severity, const char *errmsg, pg_GSS_error(const char *errmsg,
OM_uint32 maj_stat, OM_uint32 min_stat) OM_uint32 maj_stat, OM_uint32 min_stat)
{ {
char msg_major[128], char msg_major[128],
@ -68,7 +87,7 @@ pg_GSS_error(int severity, const char *errmsg,
* errmsg_internal, since translation of the first part must be done * errmsg_internal, since translation of the first part must be done
* before calling this function anyway. * before calling this function anyway.
*/ */
ereport(severity, ereport(COMMERROR,
(errmsg_internal("%s", errmsg), (errmsg_internal("%s", errmsg),
errdetail_internal("%s: %s", msg_major, msg_minor))); errdetail_internal("%s: %s", msg_major, msg_minor)));
} }

View File

@ -21,6 +21,7 @@
#include "libpq/pqformat.h" #include "libpq/pqformat.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "pgstat.h" #include "pgstat.h"
#include "utils/memutils.h"
/* /*
@ -81,10 +82,14 @@ static uint32 PqGSSMaxPktSize; /* Maximum size we can encrypt and fit the
* transport negotiation is complete). * transport negotiation is complete).
* *
* On success, returns the number of data bytes consumed (possibly less than * On success, returns the number of data bytes consumed (possibly less than
* len). On failure, returns -1 with errno set appropriately. (For fatal * len). On failure, returns -1 with errno set appropriately. For retryable
* errors, we may just elog and exit, if errno wouldn't be sufficient to * errors, caller should call again (passing the same data) once the socket
* describe the error.) For retryable errors, caller should call again * is ready.
* (passing the same data) once the socket is ready. *
* Dealing with fatal errors here is a bit tricky: we can't invoke elog(FATAL)
* since it would try to write to the client, probably resulting in infinite
* recursion. Instead, use elog(COMMERROR) to log extra info about the
* failure if necessary, and then return an errno indicating connection loss.
*/ */
ssize_t ssize_t
be_gssapi_write(Port *port, void *ptr, size_t len) be_gssapi_write(Port *port, void *ptr, size_t len)
@ -108,8 +113,11 @@ be_gssapi_write(Port *port, void *ptr, size_t len)
* again, so if it offers a len less than that, something is wrong. * again, so if it offers a len less than that, something is wrong.
*/ */
if (len < PqGSSSendConsumed) if (len < PqGSSSendConsumed)
elog(FATAL, "GSSAPI caller failed to retransmit all data needing to be retried"); {
elog(COMMERROR, "GSSAPI caller failed to retransmit all data needing to be retried");
errno = ECONNRESET;
return -1;
}
/* Discount whatever source data we already encrypted. */ /* Discount whatever source data we already encrypted. */
bytes_to_encrypt = len - PqGSSSendConsumed; bytes_to_encrypt = len - PqGSSSendConsumed;
bytes_encrypted = PqGSSSendConsumed; bytes_encrypted = PqGSSSendConsumed;
@ -192,17 +200,27 @@ be_gssapi_write(Port *port, void *ptr, size_t len)
major = gss_wrap(&minor, gctx, 1, GSS_C_QOP_DEFAULT, major = gss_wrap(&minor, gctx, 1, GSS_C_QOP_DEFAULT,
&input, &conf_state, &output); &input, &conf_state, &output);
if (major != GSS_S_COMPLETE) if (major != GSS_S_COMPLETE)
pg_GSS_error(FATAL, gettext_noop("GSSAPI wrap error"), major, minor); {
pg_GSS_error(_("GSSAPI wrap error"), major, minor);
errno = ECONNRESET;
return -1;
}
if (conf_state == 0) if (conf_state == 0)
ereport(FATAL, {
ereport(COMMERROR,
(errmsg("outgoing GSSAPI message would not use confidentiality"))); (errmsg("outgoing GSSAPI message would not use confidentiality")));
errno = ECONNRESET;
return -1;
}
if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)) if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
ereport(FATAL, {
ereport(COMMERROR,
(errmsg("server tried to send oversize GSSAPI packet (%zu > %zu)", (errmsg("server tried to send oversize GSSAPI packet (%zu > %zu)",
(size_t) output.length, (size_t) output.length,
PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)))); PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))));
errno = ECONNRESET;
return -1;
}
bytes_encrypted += input.length; bytes_encrypted += input.length;
bytes_to_encrypt -= input.length; bytes_to_encrypt -= input.length;
@ -234,9 +252,11 @@ be_gssapi_write(Port *port, void *ptr, size_t len)
* transport negotiation is complete). * transport negotiation is complete).
* *
* Returns the number of data bytes read, or on failure, returns -1 * Returns the number of data bytes read, or on failure, returns -1
* with errno set appropriately. (For fatal errors, we may just elog and * with errno set appropriately. For retryable errors, caller should call
* exit, if errno wouldn't be sufficient to describe the error.) For * again once the socket is ready.
* retryable errors, caller should call again once the socket is ready. *
* We treat fatal errors the same as in be_gssapi_write(), even though the
* argument about infinite recursion doesn't apply here.
*/ */
ssize_t ssize_t
be_gssapi_read(Port *port, void *ptr, size_t len) be_gssapi_read(Port *port, void *ptr, size_t len)
@ -326,10 +346,14 @@ be_gssapi_read(Port *port, void *ptr, size_t len)
input.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer); input.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer);
if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)) if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))
ereport(FATAL, {
ereport(COMMERROR,
(errmsg("oversize GSSAPI packet sent by the client (%zu > %zu)", (errmsg("oversize GSSAPI packet sent by the client (%zu > %zu)",
(size_t) input.length, (size_t) input.length,
PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)))); PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))));
errno = ECONNRESET;
return -1;
}
/* /*
* Read as much of the packet as we are able to on this call into * Read as much of the packet as we are able to on this call into
@ -361,12 +385,18 @@ be_gssapi_read(Port *port, void *ptr, size_t len)
major = gss_unwrap(&minor, gctx, &input, &output, &conf_state, NULL); major = gss_unwrap(&minor, gctx, &input, &output, &conf_state, NULL);
if (major != GSS_S_COMPLETE) if (major != GSS_S_COMPLETE)
pg_GSS_error(FATAL, gettext_noop("GSSAPI unwrap error"), {
major, minor); pg_GSS_error(_("GSSAPI unwrap error"), major, minor);
errno = ECONNRESET;
return -1;
}
if (conf_state == 0) if (conf_state == 0)
ereport(FATAL, {
ereport(COMMERROR,
(errmsg("incoming GSSAPI message did not use confidentiality"))); (errmsg("incoming GSSAPI message did not use confidentiality")));
errno = ECONNRESET;
return -1;
}
memcpy(PqGSSResultBuffer, output.value, output.length); memcpy(PqGSSResultBuffer, output.value, output.length);
PqGSSResultLength = output.length; PqGSSResultLength = output.length;
@ -468,6 +498,12 @@ secure_open_gssapi(Port *port)
OM_uint32 major, OM_uint32 major,
minor; minor;
/*
* Allocate subsidiary Port data for GSSAPI operations.
*/
port->gss = (pg_gssinfo *)
MemoryContextAllocZero(TopMemoryContext, sizeof(pg_gssinfo));
/* /*
* Allocate buffers and initialize state variables. By malloc'ing the * Allocate buffers and initialize state variables. By malloc'ing the
* buffers at this point, we avoid wasting static data space in processes * buffers at this point, we avoid wasting static data space in processes
@ -521,10 +557,13 @@ secure_open_gssapi(Port *port)
* Verify on our side that the client doesn't do something funny. * Verify on our side that the client doesn't do something funny.
*/ */
if (input.length > PQ_GSS_RECV_BUFFER_SIZE) if (input.length > PQ_GSS_RECV_BUFFER_SIZE)
ereport(FATAL, {
ereport(COMMERROR,
(errmsg("oversize GSSAPI packet sent by the client (%zu > %d)", (errmsg("oversize GSSAPI packet sent by the client (%zu > %d)",
(size_t) input.length, (size_t) input.length,
PQ_GSS_RECV_BUFFER_SIZE))); PQ_GSS_RECV_BUFFER_SIZE)));
return -1;
}
/* /*
* Get the rest of the packet so we can pass it to GSSAPI to accept * Get the rest of the packet so we can pass it to GSSAPI to accept
@ -544,7 +583,7 @@ secure_open_gssapi(Port *port)
NULL, NULL); NULL, NULL);
if (GSS_ERROR(major)) if (GSS_ERROR(major))
{ {
pg_GSS_error(ERROR, gettext_noop("could not accept GSSAPI security context"), pg_GSS_error(_("could not accept GSSAPI security context"),
major, minor); major, minor);
gss_release_buffer(&minor, &output); gss_release_buffer(&minor, &output);
return -1; return -1;
@ -570,10 +609,14 @@ secure_open_gssapi(Port *port)
uint32 netlen = pg_hton32(output.length); uint32 netlen = pg_hton32(output.length);
if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)) if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
ereport(FATAL, {
ereport(COMMERROR,
(errmsg("server tried to send oversize GSSAPI packet (%zu > %zu)", (errmsg("server tried to send oversize GSSAPI packet (%zu > %zu)",
(size_t) output.length, (size_t) output.length,
PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)))); PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))));
gss_release_buffer(&minor, &output);
return -1;
}
memcpy(PqGSSSendBuffer, (char *) &netlen, sizeof(uint32)); memcpy(PqGSSSendBuffer, (char *) &netlen, sizeof(uint32));
PqGSSSendLength += sizeof(uint32); PqGSSSendLength += sizeof(uint32);
@ -634,8 +677,10 @@ secure_open_gssapi(Port *port)
&PqGSSMaxPktSize); &PqGSSMaxPktSize);
if (GSS_ERROR(major)) if (GSS_ERROR(major))
pg_GSS_error(FATAL, gettext_noop("GSSAPI size check error"), {
major, minor); pg_GSS_error(_("GSSAPI size check error"), major, minor);
return -1;
}
port->gss->enc = true; port->gss->enc = true;
@ -667,12 +712,13 @@ be_gssapi_get_enc(Port *port)
} }
/* /*
* Return the GSSAPI principal used for authentication on this connection. * Return the GSSAPI principal used for authentication on this connection
* (NULL if we did not perform GSSAPI authentication).
*/ */
const char * const char *
be_gssapi_get_princ(Port *port) be_gssapi_get_princ(Port *port)
{ {
if (!port || !port->gss->auth) if (!port || !port->gss)
return NULL; return NULL;
return port->gss->princ; return port->gss->princ;

View File

@ -160,7 +160,7 @@ retry:
else else
#endif #endif
#ifdef ENABLE_GSS #ifdef ENABLE_GSS
if (port->gss->enc) if (port->gss && port->gss->enc)
{ {
n = be_gssapi_read(port, ptr, len); n = be_gssapi_read(port, ptr, len);
waitfor = WL_SOCKET_READABLE; waitfor = WL_SOCKET_READABLE;
@ -273,7 +273,7 @@ retry:
else else
#endif #endif
#ifdef ENABLE_GSS #ifdef ENABLE_GSS
if (port->gss->enc) if (port->gss && port->gss->enc)
{ {
n = be_gssapi_write(port, ptr, len); n = be_gssapi_write(port, ptr, len);
waitfor = WL_SOCKET_WRITEABLE; waitfor = WL_SOCKET_WRITEABLE;

View File

@ -1447,19 +1447,6 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
*err_msg = "gssapi authentication is not supported on local sockets"; *err_msg = "gssapi authentication is not supported on local sockets";
return NULL; return NULL;
} }
if (parsedline->conntype == ctHostGSS &&
parsedline->auth_method != uaGSS &&
parsedline->auth_method != uaReject &&
parsedline->auth_method != uaTrust)
{
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("GSSAPI encryption only supports gss, trust, or reject authentication"),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "GSSAPI encryption only supports gss, trust, or reject authentication";
return NULL;
}
if (parsedline->conntype != ctLocal && if (parsedline->conntype != ctLocal &&
parsedline->auth_method == uaPeer) parsedline->auth_method == uaPeer)
@ -2134,9 +2121,11 @@ check_hba(hbaPort *port)
/* Check GSSAPI state */ /* Check GSSAPI state */
#ifdef ENABLE_GSS #ifdef ENABLE_GSS
if (port->gss->enc && hba->conntype == ctHostNoGSS) if (port->gss && port->gss->enc &&
hba->conntype == ctHostNoGSS)
continue; continue;
else if (!port->gss->enc && hba->conntype == ctHostGSS) else if (!(port->gss && port->gss->enc) &&
hba->conntype == ctHostGSS)
continue; continue;
#else #else
if (hba->conntype == ctHostGSS) if (hba->conntype == ctHostGSS)

View File

@ -256,30 +256,27 @@ socket_close(int code, Datum arg)
/* Nothing to do in a standalone backend, where MyProcPort is NULL. */ /* Nothing to do in a standalone backend, where MyProcPort is NULL. */
if (MyProcPort != NULL) if (MyProcPort != NULL)
{ {
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
#ifdef ENABLE_GSS #ifdef ENABLE_GSS
OM_uint32 min_s;
/* /*
* Shutdown GSSAPI layer. This section does nothing when interrupting * Shutdown GSSAPI layer. This section does nothing when interrupting
* BackendInitialize(), because pg_GSS_recvauth() makes first use of * BackendInitialize(), because pg_GSS_recvauth() makes first use of
* "ctx" and "cred". * "ctx" and "cred".
*
* Note that we don't bother to free MyProcPort->gss, since we're
* about to exit anyway.
*/ */
if (MyProcPort->gss->ctx != GSS_C_NO_CONTEXT) if (MyProcPort->gss)
gss_delete_sec_context(&min_s, &MyProcPort->gss->ctx, NULL); {
OM_uint32 min_s;
if (MyProcPort->gss->cred != GSS_C_NO_CREDENTIAL) if (MyProcPort->gss->ctx != GSS_C_NO_CONTEXT)
gss_release_cred(&min_s, &MyProcPort->gss->cred); gss_delete_sec_context(&min_s, &MyProcPort->gss->ctx, NULL);
if (MyProcPort->gss->cred != GSS_C_NO_CREDENTIAL)
gss_release_cred(&min_s, &MyProcPort->gss->cred);
}
#endif /* ENABLE_GSS */ #endif /* ENABLE_GSS */
/*
* GSS and SSPI share the port->gss struct. Since nowhere else does a
* postmaster child free this, doing so is safe when interrupting
* BackendInitialize().
*/
free(MyProcPort->gss);
#endif /* ENABLE_GSS || ENABLE_SSPI */
/* /*
* Cleanly shut down SSL layer. Nowhere else does a postmaster child * Cleanly shut down SSL layer. Nowhere else does a postmaster child
* call this, so this is safe when interrupting BackendInitialize(). * call this, so this is safe when interrupting BackendInitialize().

View File

@ -2888,6 +2888,11 @@ BackendStatusShmemSize(void)
/* BackendSslStatusBuffer: */ /* BackendSslStatusBuffer: */
size = add_size(size, size = add_size(size,
mul_size(sizeof(PgBackendSSLStatus), NumBackendStatSlots)); mul_size(sizeof(PgBackendSSLStatus), NumBackendStatSlots));
#endif
#ifdef ENABLE_GSS
/* BackendGssStatusBuffer: */
size = add_size(size,
mul_size(sizeof(PgBackendGSSStatus), NumBackendStatSlots));
#endif #endif
return size; return size;
} }
@ -3172,12 +3177,13 @@ pgstat_bestart(void)
#ifdef ENABLE_GSS #ifdef ENABLE_GSS
if (MyProcPort && MyProcPort->gss != NULL) if (MyProcPort && MyProcPort->gss != NULL)
{ {
const char *princ = be_gssapi_get_princ(MyProcPort);
lbeentry.st_gss = true; lbeentry.st_gss = true;
lgssstatus.gss_auth = be_gssapi_get_auth(MyProcPort); lgssstatus.gss_auth = be_gssapi_get_auth(MyProcPort);
lgssstatus.gss_enc = be_gssapi_get_enc(MyProcPort); lgssstatus.gss_enc = be_gssapi_get_enc(MyProcPort);
if (princ)
if (lgssstatus.gss_auth) strlcpy(lgssstatus.gss_princ, princ, NAMEDATALEN);
strlcpy(lgssstatus.gss_princ, be_gssapi_get_princ(MyProcPort), NAMEDATALEN);
} }
else else
{ {

View File

@ -2052,6 +2052,7 @@ retry1:
else if (proto == NEGOTIATE_GSS_CODE && !gss_done) else if (proto == NEGOTIATE_GSS_CODE && !gss_done)
{ {
char GSSok = 'N'; char GSSok = 'N';
#ifdef ENABLE_GSS #ifdef ENABLE_GSS
/* No GSSAPI encryption when on Unix socket */ /* No GSSAPI encryption when on Unix socket */
if (!IS_AF_UNIX(port->laddr.addr.ss_family)) if (!IS_AF_UNIX(port->laddr.addr.ss_family))
@ -2520,37 +2521,19 @@ ConnCreate(int serverFd)
return NULL; return NULL;
} }
/*
* Allocate GSSAPI specific state struct
*/
#ifndef EXEC_BACKEND
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
port->gss = (pg_gssinfo *) calloc(1, sizeof(pg_gssinfo));
if (!port->gss)
{
ereport(LOG,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
ExitPostmaster(1);
}
#endif
#endif
return port; return port;
} }
/* /*
* ConnFree -- free a local connection data structure * ConnFree -- free a local connection data structure
*
* Caller has already closed the socket if any, so there's not much
* to do here.
*/ */
static void static void
ConnFree(Port *conn) ConnFree(Port *conn)
{ {
#ifdef USE_SSL
secure_close(conn);
#endif
if (conn->gss)
free(conn->gss);
free(conn); free(conn);
} }
@ -4906,18 +4889,6 @@ SubPostmasterMain(int argc, char *argv[])
/* Setup as postmaster child */ /* Setup as postmaster child */
InitPostmasterChild(); InitPostmasterChild();
/*
* Set up memory area for GSS information. Mirrors the code in ConnCreate
* for the non-exec case.
*/
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
port.gss = (pg_gssinfo *) calloc(1, sizeof(pg_gssinfo));
if (!port.gss)
ereport(FATAL,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
#endif
/* /*
* If appropriate, physically re-attach to shared memory segment. We want * If appropriate, physically re-attach to shared memory segment. We want
* to do this before going any further to ensure that we can attach at the * to do this before going any further to ensure that we can attach at the

View File

@ -270,11 +270,22 @@ PerformAuthentication(Port *port)
be_tls_get_compression(port) ? _("on") : _("off")); be_tls_get_compression(port) ? _("on") : _("off"));
#endif #endif
#ifdef ENABLE_GSS #ifdef ENABLE_GSS
if (be_gssapi_get_princ(port)) if (port->gss)
appendStringInfo(&logmsg, _(" GSS (authenticated=%s, encrypted=%s, principal=%s)"), {
be_gssapi_get_auth(port) ? _("yes") : _("no"), const char *princ = be_gssapi_get_princ(port);
be_gssapi_get_enc(port) ? _("yes") : _("no"),
be_gssapi_get_princ(port)); if (princ)
appendStringInfo(&logmsg,
_(" GSS (authenticated=%s, encrypted=%s, principal=%s)"),
be_gssapi_get_auth(port) ? _("yes") : _("no"),
be_gssapi_get_enc(port) ? _("yes") : _("no"),
princ);
else
appendStringInfo(&logmsg,
_(" GSS (authenticated=%s, encrypted=%s)"),
be_gssapi_get_auth(port) ? _("yes") : _("no"),
be_gssapi_get_enc(port) ? _("yes") : _("no"));
}
#endif #endif
ereport(LOG, errmsg_internal("%s", logmsg.data)); ereport(LOG, errmsg_internal("%s", logmsg.data));

View File

@ -20,7 +20,7 @@
#include <gssapi/gssapi.h> #include <gssapi/gssapi.h>
#endif #endif
void pg_GSS_error(int severity, const char *errmsg, extern void pg_GSS_error(const char *errmsg,
OM_uint32 maj_stat, OM_uint32 min_stat); OM_uint32 maj_stat, OM_uint32 min_stat);
#endif /* BE_GSSAPI_COMMON_H */ #endif /* BE_GSSAPI_COMMON_H */

View File

@ -176,8 +176,9 @@ typedef struct Port
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI) #if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
/* /*
* If GSSAPI is supported, store GSSAPI information. Otherwise, store a * If GSSAPI is supported and used on this connection, store GSSAPI
* NULL pointer to make sure offsets in the struct remain the same. * information. Even when GSSAPI is not compiled in, store a NULL pointer
* to keep struct offsets the same (for extension ABI compatibility).
*/ */
pg_gssinfo *gss; pg_gssinfo *gss;
#else #else