1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-15 19:21:59 +03:00

GSSAPI encryption support

On both the frontend and backend, prepare for GSSAPI encryption
support by moving common code for error handling into a separate file.
Fix a TODO for handling multiple status messages in the process.
Eliminate the OIDs, which have not been needed for some time.

Add frontend and backend encryption support functions.  Keep the
context initiation for authentication-only separate on both the
frontend and backend in order to avoid concerns about changing the
requested flags to include encryption support.

In postmaster, pull GSSAPI authorization checking into a shared
function.  Also share the initiator name between the encryption and
non-encryption codepaths.

For HBA, add "hostgssenc" and "hostnogssenc" entries that behave
similarly to their SSL counterparts.  "hostgssenc" requires either
"gss", "trust", or "reject" for its authentication.

Similarly, add a "gssencmode" parameter to libpq.  Supported values are
"disable", "require", and "prefer".  Notably, negotiation will only be
attempted if credentials can be acquired.  Move credential acquisition
into its own function to support this behavior.

Add a simple pg_stat_gssapi view similar to pg_stat_ssl, for monitoring
if GSSAPI authentication was used, what principal was used, and if
encryption is being used on the connection.

Finally, add documentation for everything new, and update existing
documentation on connection security.

Thanks to Michael Paquier for the Windows fixes.

Author: Robbie Harwood, with changes to the read/write functions by me.
Reviewed in various forms and at different times by: Michael Paquier,
   Andres Freund, David Steele.
Discussion: https://www.postgresql.org/message-id/flat/jlg1tgq1ktm.fsf@thriss.redhat.com
This commit is contained in:
Stephen Frost
2019-04-03 15:02:33 -04:00
parent 5f6fc34af5
commit b0b39f72b9
35 changed files with 2575 additions and 197 deletions

View File

@ -129,6 +129,12 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
#else
#define DefaultSSLMode "disable"
#endif
#ifdef ENABLE_GSS
#include "fe-gssapi-common.h"
#define DefaultGSSMode "prefer"
#else
#define DefaultGSSMode "disable"
#endif
/* ----------
* Definition of the conninfo parameters and their fallback resources.
@ -298,6 +304,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
/*
* Expose gssencmode similarly to sslmode - we can still handle "disable"
* and "prefer".
*/
{"gssencmode", "PGGSSMODE", DefaultGSSMode, NULL,
"GSS-Mode", "", 7, /* sizeof("disable") == 7 */
offsetof(struct pg_conn, gssencmode)},
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
/* Kerberos and GSSAPI authentication support specifying the service name */
{"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL,
@ -1226,6 +1240,39 @@ connectOptions2(PGconn *conn)
goto oom_error;
}
/*
* validate gssencmode option
*/
if (conn->gssencmode)
{
if (strcmp(conn->gssencmode, "disable") != 0 &&
strcmp(conn->gssencmode, "prefer") != 0 &&
strcmp(conn->gssencmode, "require") != 0)
{
conn->status = CONNECTION_BAD;
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("invalid gssencmode value: \"%s\"\n"),
conn->gssencmode);
return false;
}
#ifndef ENABLE_GSS
if (strcmp(conn->gssencmode, "require") == 0)
{
conn->status = CONNECTION_BAD;
printfPQExpBuffer(
&conn->errorMessage,
libpq_gettext("no GSSAPI support; cannot require GSSAPI\n"));
return false;
}
#endif
}
else
{
conn->gssencmode = strdup(DefaultGSSMode);
if (!conn->gssencmode)
goto oom_error;
}
/*
* Resolve special "auto" client_encoding from the locale
*/
@ -1827,6 +1874,11 @@ connectDBStart(PGconn *conn)
*/
resetPQExpBuffer(&conn->errorMessage);
#ifdef ENABLE_GSS
if (conn->gssencmode[0] == 'd') /* "disable" */
conn->try_gss = false;
#endif
/*
* Set up to try to connect to the first host. (Setting whichhost = -1 is
* a bit of a cheat, but PQconnectPoll will advance it to 0 before
@ -2099,6 +2151,7 @@ PQconnectPoll(PGconn *conn)
case CONNECTION_NEEDED:
case CONNECTION_CHECK_WRITABLE:
case CONNECTION_CONSUME:
case CONNECTION_GSS_STARTUP:
break;
default:
@ -2640,17 +2693,57 @@ keep_going: /* We will come back to here until there is
}
#endif /* HAVE_UNIX_SOCKETS */
if (IS_AF_UNIX(conn->raddr.addr.ss_family))
{
/* Don't request SSL or GSSAPI over Unix sockets */
#ifdef USE_SSL
conn->allow_ssl_try = false;
#endif
#ifdef ENABLE_GSS
conn->try_gss = false;
#endif
}
#ifdef ENABLE_GSS
/*
* If GSSAPI is enabled and we have a ccache, try to set it up
* before sending startup messages. If it's already
* operating, don't try SSL and instead just build the startup
* packet.
*/
if (conn->try_gss && !conn->gctx)
conn->try_gss = pg_GSS_have_ccache(&conn->gcred);
if (conn->try_gss && !conn->gctx)
{
ProtocolVersion pv = pg_hton32(NEGOTIATE_GSS_CODE);
if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK)
{
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not send GSSAPI negotiation packet: %s\n"),
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
goto error_return;
}
/* Ok, wait for response */
conn->status = CONNECTION_GSS_STARTUP;
return PGRES_POLLING_READING;
}
else if (!conn->gctx && conn->gssencmode[0] == 'r')
{
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("GSSAPI encryption required, but was impossible (possibly no ccache, no server support, or using a local socket)\n"));
goto error_return;
}
#endif
#ifdef USE_SSL
/*
* If SSL is enabled and we haven't already got it running,
* request it instead of sending the startup message.
*/
if (IS_AF_UNIX(conn->raddr.addr.ss_family))
{
/* Don't bother requesting SSL over a Unix socket */
conn->allow_ssl_try = false;
}
if (conn->allow_ssl_try && !conn->wait_ssl_try &&
!conn->ssl_in_use)
{
@ -2844,6 +2937,98 @@ keep_going: /* We will come back to here until there is
#endif /* USE_SSL */
}
case CONNECTION_GSS_STARTUP:
{
#ifdef ENABLE_GSS
PostgresPollingStatusType pollres;
/*
* If we haven't yet, get the postmaster's response to our
* negotiation packet
*/
if (conn->try_gss && !conn->gctx)
{
char gss_ok;
int rdresult = pqReadData(conn);
if (rdresult < 0)
/* pqReadData fills in error message */
goto error_return;
else if (rdresult == 0)
/* caller failed to wait for data */
return PGRES_POLLING_READING;
if (pqGetc(&gss_ok, conn) < 0)
/* shouldn't happen... */
return PGRES_POLLING_READING;
if (gss_ok == 'E')
{
/*
* Server failure of some sort. Assume it's a
* protocol version support failure, and let's see if
* we can't recover (if it's not, we'll get a better
* error message on retry). Server gets fussy if we
* don't hang up the socket, though.
*/
conn->try_gss = false;
pqDropConnection(conn, true);
conn->status = CONNECTION_NEEDED;
goto keep_going;
}
/* mark byte consumed */
conn->inStart = conn->inCursor;
if (gss_ok == 'N')
{
/* Server doesn't want GSSAPI; fall back if we can */
if (conn->gssencmode[0] == 'r')
{
appendPQExpBufferStr(&conn->errorMessage,
libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n"));
goto error_return;
}
conn->try_gss = false;
conn->status = CONNECTION_MADE;
return PGRES_POLLING_WRITING;
}
else if (gss_ok != 'G')
{
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"),
gss_ok);
goto error_return;
}
}
/* Begin or continue GSSAPI negotiation */
pollres = pqsecure_open_gss(conn);
if (pollres == PGRES_POLLING_OK)
{
/* All set for startup packet */
conn->status = CONNECTION_MADE;
return PGRES_POLLING_WRITING;
}
else if (pollres == PGRES_POLLING_FAILED &&
conn->gssencmode[0] == 'p')
{
/*
* We failed, but we can retry on "prefer". Have to drop
* the current connection to do so, though.
*/
conn->try_gss = false;
pqDropConnection(conn, true);
conn->status = CONNECTION_NEEDED;
goto keep_going;
}
return pollres;
#else /* !ENABLE_GSS */
/* unreachable */
goto error_return;
#endif /* ENABLE_GSS */
}
/*
* Handle authentication exchange: wait for postmaster messages
* and respond as necessary.
@ -2997,6 +3182,26 @@ keep_going: /* We will come back to here until there is
/* Check to see if we should mention pgpassfile */
pgpassfileWarning(conn);
#ifdef ENABLE_GSS
/*
* If gssencmode is "prefer" and we're using GSSAPI, retry
* without it.
*/
if (conn->gssenc && conn->gssencmode[0] == 'p')
{
OM_uint32 minor;
/* postmaster expects us to drop the connection */
conn->try_gss = false;
conn->gssenc = false;
gss_delete_sec_context(&minor, &conn->gctx, NULL);
pqDropConnection(conn, true);
conn->status = CONNECTION_NEEDED;
goto keep_going;
}
#endif
#ifdef USE_SSL
/*
@ -3564,6 +3769,9 @@ makeEmptyPGconn(void)
conn->verbosity = PQERRORS_DEFAULT;
conn->show_context = PQSHOW_CONTEXT_ERRORS;
conn->sock = PGINVALID_SOCKET;
#ifdef ENABLE_GSS
conn->try_gss = true;
#endif
/*
* We try to send at least 8K at a time, which is the usual size of pipe
@ -3695,10 +3903,28 @@ freePGconn(PGconn *conn)
free(conn->requirepeer);
if (conn->connip)
free(conn->connip);
if (conn->gssencmode)
free(conn->gssencmode);
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
if (conn->krbsrvname)
free(conn->krbsrvname);
#endif
#ifdef ENABLE_GSS
if (conn->gcred != GSS_C_NO_CREDENTIAL)
{
OM_uint32 minor;
gss_release_cred(&minor, &conn->gcred);
conn->gcred = GSS_C_NO_CREDENTIAL;
}
if (conn->gctx)
{
OM_uint32 minor;
gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER);
conn->gctx = NULL;
}
#endif
#if defined(ENABLE_GSS) && defined(ENABLE_SSPI)
if (conn->gsslib)
free(conn->gsslib);