Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kteb4-0006Oj-B3 for pgsql-general@arkaria.postgresql.org; Sun, 27 Dec 2020 22:33:06 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1kteb2-0007le-S7 for pgsql-general@arkaria.postgresql.org; Sun, 27 Dec 2020 22:33:04 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kteb2-0007lX-GW for pgsql-general@lists.postgresql.org; Sun, 27 Dec 2020 22:33:04 +0000 Received: from sss.pgh.pa.us ([66.207.139.130]) by magus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kteay-00039w-IS for pgsql-general@postgresql.org; Sun, 27 Dec 2020 22:33:04 +0000 Received: from sss1.sss.pgh.pa.us (localhost [127.0.0.1]) by sss.pgh.pa.us (8.15.2/8.15.2) with ESMTP id 0BRMWsOb1528844; Sun, 27 Dec 2020 17:32:54 -0500 From: Tom Lane To: Stephen Frost cc: Gustavsson Mikael , Magnus Hagander , Kyotaro Horiguchi , "pgsql-general@postgresql.org" , Svensson Peter Subject: Re: Problem with ssl and psql in Postgresql 13 In-reply-to: <1333636.1609020377@sss.pgh.pa.us> References: <89909.1608222331@sss.pgh.pa.us> <61e2a65bbc2f47d297a6f3cdac2cc6cc@smhi.se> <169637.1608321770@sss.pgh.pa.us> <723586.1608740901@sss.pgh.pa.us> <737386.1608757126@sss.pgh.pa.us> <20201223211448.GR27507@tamriel.snowman.net> <738715.1608758847@sss.pgh.pa.us> <20201223214534.GS27507@tamriel.snowman.net> <740093.1608760567@sss.pgh.pa.us> <742641.1608762845@sss.pgh.pa.us> <1308863.1609016410@sss.pgh.pa.us> <1333636.1609020377@sss.pgh.pa.us> Comments: In-reply-to Tom Lane message dated "Sat, 26 Dec 2020 17:06:17 -0500" MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0" Content-ID: <1528789.1609108300.0@sss.pgh.pa.us> Date: Sun, 27 Dec 2020 17:32:54 -0500 Message-ID: <1528843.1609108374@sss.pgh.pa.us> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk ------- =_aaaaaaaaaa0 Content-Type: text/plain; charset="us-ascii" Content-ID: <1528789.1609108300.1@sss.pgh.pa.us> Here's a more complete patchset. 0001 is the same libpq fixes I posted before. 0002 fixes a collection of random server-side issues, including: * The GSS encryption code figured it could just ereport(ERROR) or ereport(FATAL) for unrecoverable errors. This seems entirely unsafe, because elog.c will try to send the error message to the client, likely re-triggering the same error and leading to infinite recursion. Even if that doesn't happen, we might have sent a partial packet to the client meaning that protocol synchronization is lost. The right thing to do is like what the SSL encryption code has long done: log the error at COMMERROR level and then return errno = ECONNRESET to shut things down. * Fix sloppiness about whether pg_GSS_error()'s message parameter is already translated or not. (BTW, I'm a bit inclined to remove pg_GSS_error()'s elevel parameter altogether and hardwire it as COMMERROR, thus removing a bug temptation and making it more like the similarly-named frontend function. I didn't pull the trigger on that here, though.) * I really didn't like that we had the postmaster allocating the port->gss struct (and committing hara-kiri if it could not). Admittedly, the struct is not so large that an OOM failure is likely, but it's just dumb to make the postmaster allocate a struct it has zero use for. I moved that allocation to the code that actually needs it, GSSAPI encryption or auth startup. * That also allows fixing a bug I noticed earlier, that the "connection authorized" log message fails to mention GSS if we are only using GSS for encryption without invoking GSS auth. We can use whether or not port->gss is non-null to figure out whether the GSS clause of the message should appear. (This is related to, but distinct from, the pg_hba.conf-related log message fixes discussed upthread. I've not included those changes here, but they're still on the table.) * BackendStatusShmemSize failed to account for GSS-related space. With large max_connections this could make a serious dent in our shared-memory slop space. * Various places knew more than they needed to about whether port->gss->auth being set is equivalent to port->gss->princ being set. * Remove long-dead call of secure_close() in postmaster's ConnFree subroutine. The 0003 patch removes the restriction I griped of earlier about which auth methods can be used on a GSS-encrypted connection. I made that a separate patch just in case it's controversial. Finally, 0004 tries to improve the documentation in this area. Some of that is reflective of 0003, but most of it is just cleanup. I propose to back-patch all of this as far as v12. regards, tom lane ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="0001-fix-libpq-gss-bugs.patch"; charset="us-ascii" Content-ID: <1528789.1609108300.2@sss.pgh.pa.us> Content-Description: 0001-fix-libpq-gss-bugs.patch Content-Transfer-Encoding: quoted-printable diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-c= onnect.c index 7d04d3664e..ec7c4c5e87 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -2909,11 +2909,16 @@ keep_going: /* We will come back to here unti= l there is #ifdef USE_SSL = /* - * If SSL is enabled and we haven't already got it running, - * request it instead of sending the startup message. + * If SSL is enabled and we haven't already got encryption of + * some sort running, request SSL instead of sending the + * startup message. */ if (conn->allow_ssl_try && !conn->wait_ssl_try && - !conn->ssl_in_use) + !conn->ssl_in_use +#ifdef ENABLE_GSS + && !conn->gssenc +#endif + ) { ProtocolVersion pv; = @@ -3042,6 +3047,7 @@ keep_going: /* We will come back to here until = there is } /* Otherwise, proceed with normal startup */ conn->allow_ssl_try =3D false; + /* We can proceed using this connection */ conn->status =3D CONNECTION_MADE; return PGRES_POLLING_WRITING; } @@ -3139,8 +3145,7 @@ keep_going: /* We will come back to here until = there is * don't hang up the socket, though. */ conn->try_gss =3D false; - pqDropConnection(conn, true); - conn->status =3D CONNECTION_NEEDED; + need_new_connection =3D true; goto keep_going; } = @@ -3158,6 +3163,7 @@ keep_going: /* We will come back to here until = there is } = conn->try_gss =3D false; + /* We can proceed using this connection */ conn->status =3D CONNECTION_MADE; return PGRES_POLLING_WRITING; } @@ -3186,8 +3192,7 @@ keep_going: /* We will come back to here until = there is * the current connection to do so, though. */ conn->try_gss =3D false; - pqDropConnection(conn, true); - conn->status =3D CONNECTION_NEEDED; + need_new_connection =3D true; goto keep_going; } return pollres; @@ -3354,10 +3359,9 @@ keep_going: /* We will come back to here until= there is */ if (conn->gssenc && conn->gssencmode[0] =3D=3D 'p') { - /* postmaster expects us to drop the connection */ + /* only retry once */ conn->try_gss =3D false; - pqDropConnection(conn, true); - conn->status =3D CONNECTION_NEEDED; + need_new_connection =3D true; goto keep_going; } #endif diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libp= q/fe-secure-gssapi.c index bfc0f55214..9416306eea 100644 --- a/src/interfaces/libpq/fe-secure-gssapi.c +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -647,17 +647,14 @@ pqsecure_open_gss(PGconn *conn) if (output.length =3D=3D 0) { /* - * We're done - hooray! Kind of gross, but we need to disable SSL - * here so that we don't accidentally tunnel one over the other. + * We're done - hooray! Set flag to tell the low-level I/O routines + * to do GSS wrapping/unwrapping. */ -#ifdef USE_SSL - conn->allow_ssl_try =3D false; -#endif + conn->gssenc =3D true; = /* Clean up */ gss_release_cred(&minor, &conn->gcred); conn->gcred =3D GSS_C_NO_CREDENTIAL; - conn->gssenc =3D true; gss_release_buffer(&minor, &output); = /* ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="0002-fix-server-gss-issues.patch"; charset="us-ascii" Content-ID: <1528789.1609108300.3@sss.pgh.pa.us> Content-Description: 0002-fix-server-gss-issues.patch Content-Transfer-Encoding: quoted-printable diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 3d80930968..7f65310c14 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -383,7 +383,7 @@ ClientAuthentication(Port *port) } = #ifdef ENABLE_GSS - if (port->gss->enc && port->hba->auth_method !=3D uaReject && + if (port->gss && port->gss->enc && port->hba->auth_method !=3D uaReject = && port->hba->auth_method !=3D uaImplicitReject && port->hba->auth_method !=3D uaTrust && port->hba->auth_method !=3D uaGSS) @@ -533,7 +533,17 @@ ClientAuthentication(Port *port) = case uaGSS: #ifdef ENABLE_GSS + /* We might or might not have the gss workspace already */ + if (port->gss =3D=3D NULL) + port->gss =3D (pg_gssinfo *) + MemoryContextAllocZero(TopMemoryContext, + sizeof(pg_gssinfo)); port->gss->auth =3D 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) status =3D pg_GSS_checkauth(port); else @@ -548,6 +558,10 @@ ClientAuthentication(Port *port) = case uaSSPI: #ifdef ENABLE_SSPI + if (port->gss =3D=3D NULL) + port->gss =3D (pg_gssinfo *) + MemoryContextAllocZero(TopMemoryContext, + sizeof(pg_gssinfo)); sendAuthRequest(port, AUTH_REQ_SSPI, NULL, 0); status =3D pg_SSPI_recvauth(port); #else @@ -1185,9 +1199,10 @@ pg_GSS_recvauth(Port *port) if (maj_stat !=3D GSS_S_COMPLETE && maj_stat !=3D GSS_S_CONTINUE_NEEDED= ) { gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER); - pg_GSS_error(ERROR, + pg_GSS_error(COMMERROR, _("accepting GSS security context failed"), maj_stat, min_stat); + return STATUS_ERROR; } = if (maj_stat =3D=3D GSS_S_CONTINUE_NEEDED) @@ -1224,9 +1239,12 @@ pg_GSS_checkauth(Port *port) */ maj_stat =3D gss_display_name(&min_stat, port->gss->name, &gbuf, NULL); if (maj_stat !=3D GSS_S_COMPLETE) - pg_GSS_error(ERROR, + { + pg_GSS_error(COMMERROR, _("retrieving GSS user name failed"), maj_stat, min_stat); + return STATUS_ERROR; + } = /* * Copy the original name of the authenticated principal into our backen= d diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-g= ssapi-common.c index ef9b8ebd6d..a7006a0ab7 100644 --- a/src/backend/libpq/be-gssapi-common.c +++ b/src/backend/libpq/be-gssapi-common.c @@ -42,14 +42,22 @@ pg_GSS_error_int(char *s, size_t len, OM_uint32 stat, = int type) while (msg_ctx && i < len); = if (msg_ctx || i =3D=3D len) - ereport(WARNING, - (errmsg_internal("incomplete GSS error report"))); + elog(COMMERROR, "incomplete GSS error report"); } = /* - * Fetch and report all error messages from GSSAPI. To avoid 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. + * Report the GSSAPI error described by maj_stat/min_stat. + * + * errmsg should be an already-translated primary error message. + * The GSSAPI info is appended as errdetail. + * + * Caution: although we allow the caller to specify the ereport severity, + * passing anything but COMMERROR is almost certainly wrong, as it'd 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 f= or + * each of major and minor). No known mechanisms will produce error mess= ages + * beyond this cap. */ void pg_GSS_error(int severity, const char *errmsg, diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-s= ecure-gssapi.c index 5a73302b7b..b70d74ca83 100644 --- a/src/backend/libpq/be-secure-gssapi.c +++ b/src/backend/libpq/be-secure-gssapi.c @@ -21,6 +21,7 @@ #include "libpq/pqformat.h" #include "miscadmin.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). * * On success, returns the number of data bytes consumed (possibly less t= han - * len). On failure, returns -1 with errno set appropriately. (For fata= l - * errors, we may just elog and exit, if errno wouldn't be sufficient to - * describe the error.) For retryable errors, caller should call again - * (passing the same data) once the socket is ready. + * len). On failure, returns -1 with errno set appropriately. For retry= able + * errors, caller should call again (passing the same data) once the sock= et + * is ready. + * + * Dealing with fatal errors here is a bit tricky: we can't invoke elog(F= ATAL) + * since it would try to write to the client, probably resulting in infin= ite + * recursion. Instead, use elog(COMMERROR) to log extra info about the + * failure if necessary, and then return an errno indicating connection l= oss. */ ssize_t 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. */ 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 =3D ECONNRESET; + return -1; + } /* Discount whatever source data we already encrypted. */ bytes_to_encrypt =3D len - PqGSSSendConsumed; bytes_encrypted =3D PqGSSSendConsumed; @@ -192,17 +200,27 @@ be_gssapi_write(Port *port, void *ptr, size_t len) major =3D gss_wrap(&minor, gctx, 1, GSS_C_QOP_DEFAULT, &input, &conf_state, &output); if (major !=3D GSS_S_COMPLETE) - pg_GSS_error(FATAL, gettext_noop("GSSAPI wrap error"), major, minor); - + { + pg_GSS_error(COMMERROR, _("GSSAPI wrap error"), major, minor); + errno =3D ECONNRESET; + return -1; + } if (conf_state =3D=3D 0) - ereport(FATAL, + { + ereport(COMMERROR, (errmsg("outgoing GSSAPI message would not use confidentiality"))); - + errno =3D ECONNRESET; + return -1; + } if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)) - ereport(FATAL, + { + ereport(COMMERROR, (errmsg("server tried to send oversize GSSAPI packet (%zu > %zu)", (size_t) output.length, PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)))); + errno =3D ECONNRESET; + return -1; + } = bytes_encrypted +=3D input.length; bytes_to_encrypt -=3D input.length; @@ -234,9 +252,11 @@ be_gssapi_write(Port *port, void *ptr, size_t len) * transport negotiation is complete). * * Returns the number of data bytes read, or on failure, returns -1 - * with errno set appropriately. (For fatal errors, we may just elog and - * exit, if errno wouldn't be sufficient to describe the error.) For - * retryable errors, caller should call again once the socket is ready. + * with errno set appropriately. For retryable errors, caller should cal= l + * again once the socket is ready. + * + * We treat fatal errors the same as in be_gssapi_write(), even though th= e + * argument about infinite recursion doesn't apply here. */ ssize_t 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 =3D pg_ntoh32(*(uint32 *) PqGSSRecvBuffer); = if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)) - ereport(FATAL, + { + ereport(COMMERROR, (errmsg("oversize GSSAPI packet sent by the client (%zu > %zu)", (size_t) input.length, PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)))); + errno =3D ECONNRESET; + return -1; + } = /* * 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 =3D gss_unwrap(&minor, gctx, &input, &output, &conf_state, NULL); if (major !=3D GSS_S_COMPLETE) - pg_GSS_error(FATAL, gettext_noop("GSSAPI unwrap error"), - major, minor); - + { + pg_GSS_error(COMMERROR, _("GSSAPI unwrap error"), major, minor); + errno =3D ECONNRESET; + return -1; + } if (conf_state =3D=3D 0) - ereport(FATAL, + { + ereport(COMMERROR, (errmsg("incoming GSSAPI message did not use confidentiality"))); + errno =3D ECONNRESET; + return -1; + } = memcpy(PqGSSResultBuffer, output.value, output.length); PqGSSResultLength =3D output.length; @@ -468,6 +498,12 @@ secure_open_gssapi(Port *port) OM_uint32 major, minor; = + /* + * Allocate subsidiary Port data for GSSAPI operations. + */ + port->gss =3D (pg_gssinfo *) + MemoryContextAllocZero(TopMemoryContext, sizeof(pg_gssinfo)); + /* * Allocate buffers and initialize state variables. By malloc'ing the * buffers at this point, we avoid wasting static data space in processe= s @@ -521,10 +557,13 @@ secure_open_gssapi(Port *port) * Verify on our side that the client doesn't do something funny. */ if (input.length > PQ_GSS_RECV_BUFFER_SIZE) - ereport(FATAL, + { + ereport(COMMERROR, (errmsg("oversize GSSAPI packet sent by the client (%zu > %d)", (size_t) input.length, PQ_GSS_RECV_BUFFER_SIZE))); + return -1; + } = /* * 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); if (GSS_ERROR(major)) { - pg_GSS_error(ERROR, gettext_noop("could not accept GSSAPI security con= text"), + pg_GSS_error(COMMERROR, _("could not accept GSSAPI security context"), major, minor); gss_release_buffer(&minor, &output); return -1; @@ -570,10 +609,14 @@ secure_open_gssapi(Port *port) uint32 netlen =3D pg_hton32(output.length); = if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)) - ereport(FATAL, + { + ereport(COMMERROR, (errmsg("server tried to send oversize GSSAPI packet (%zu > %zu)", (size_t) output.length, PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)))); + gss_release_buffer(&minor, &output); + return -1; + } = memcpy(PqGSSSendBuffer, (char *) &netlen, sizeof(uint32)); PqGSSSendLength +=3D sizeof(uint32); @@ -634,8 +677,10 @@ secure_open_gssapi(Port *port) &PqGSSMaxPktSize); = if (GSS_ERROR(major)) - pg_GSS_error(FATAL, gettext_noop("GSSAPI size check error"), - major, minor); + { + pg_GSS_error(COMMERROR, _("GSSAPI size check error"), major, minor); + return -1; + } = port->gss->enc =3D 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 * be_gssapi_get_princ(Port *port) { - if (!port || !port->gss->auth) + if (!port || !port->gss) return NULL; = return port->gss->princ; diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index 2ae507a902..59bc02e79c 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -160,7 +160,7 @@ retry: else #endif #ifdef ENABLE_GSS - if (port->gss->enc) + if (port->gss && port->gss->enc) { n =3D be_gssapi_read(port, ptr, len); waitfor =3D WL_SOCKET_READABLE; @@ -273,7 +273,7 @@ retry: else #endif #ifdef ENABLE_GSS - if (port->gss->enc) + if (port->gss && port->gss->enc) { n =3D be_gssapi_write(port, ptr, len); waitfor =3D WL_SOCKET_WRITEABLE; diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 0cc4397769..41ca0791ea 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -2134,9 +2134,11 @@ check_hba(hbaPort *port) = /* Check GSSAPI state */ #ifdef ENABLE_GSS - if (port->gss->enc && hba->conntype =3D=3D ctHostNoGSS) + if (port->gss && port->gss->enc && + hba->conntype =3D=3D ctHostNoGSS) continue; - else if (!port->gss->enc && hba->conntype =3D=3D ctHostGSS) + else if (!(port->gss && port->gss->enc) && + hba->conntype =3D=3D ctHostGSS) continue; #else if (hba->conntype =3D=3D ctHostGSS) diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c index 0b511008fc..3ea7c6167e 100644 --- a/src/backend/libpq/pqcomm.c +++ b/src/backend/libpq/pqcomm.c @@ -256,29 +256,26 @@ socket_close(int code, Datum arg) /* Nothing to do in a standalone backend, where MyProcPort is NULL. */ if (MyProcPort !=3D NULL) { -#if defined(ENABLE_GSS) || defined(ENABLE_SSPI) #ifdef ENABLE_GSS - OM_uint32 min_s; - /* * Shutdown GSSAPI layer. This section does nothing when interrupting * BackendInitialize(), because pg_GSS_recvauth() makes first use of * "ctx" and "cred". + * + * Note that we don't bother to free MyProcPort->gss, since we're + * about to exit anyway. */ - if (MyProcPort->gss->ctx !=3D GSS_C_NO_CONTEXT) - gss_delete_sec_context(&min_s, &MyProcPort->gss->ctx, NULL); + if (MyProcPort->gss) + { + OM_uint32 min_s; = - if (MyProcPort->gss->cred !=3D GSS_C_NO_CREDENTIAL) - gss_release_cred(&min_s, &MyProcPort->gss->cred); -#endif /* ENABLE_GSS */ + if (MyProcPort->gss->ctx !=3D GSS_C_NO_CONTEXT) + gss_delete_sec_context(&min_s, &MyProcPort->gss->ctx, NULL); = - /* - * 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 */ + if (MyProcPort->gss->cred !=3D GSS_C_NO_CREDENTIAL) + gss_release_cred(&min_s, &MyProcPort->gss->cred); + } +#endif /* ENABLE_GSS */ = /* * Cleanly shut down SSL layer. Nowhere else does a postmaster child diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgst= at.c index 71f2b90ca8..7fba907479 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -2888,6 +2888,11 @@ BackendStatusShmemSize(void) /* BackendSslStatusBuffer: */ size =3D add_size(size, mul_size(sizeof(PgBackendSSLStatus), NumBackendStatSlots)); +#endif +#ifdef ENABLE_GSS + /* BackendGssStatusBuffer: */ + size =3D add_size(size, + mul_size(sizeof(PgBackendGSSStatus), NumBackendStatSlots)); #endif return size; } @@ -3172,12 +3177,13 @@ pgstat_bestart(void) #ifdef ENABLE_GSS if (MyProcPort && MyProcPort->gss !=3D NULL) { + const char *princ =3D be_gssapi_get_princ(MyProcPort); + lbeentry.st_gss =3D true; lgssstatus.gss_auth =3D be_gssapi_get_auth(MyProcPort); lgssstatus.gss_enc =3D be_gssapi_get_enc(MyProcPort); - - if (lgssstatus.gss_auth) - strlcpy(lgssstatus.gss_princ, be_gssapi_get_princ(MyProcPort), NAMEDAT= ALEN); + if (princ) + strlcpy(lgssstatus.gss_princ, princ, NAMEDATALEN); } else { diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/= postmaster.c index bf883184b1..0bb85a18f6 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -2063,6 +2063,7 @@ retry1: else if (proto =3D=3D NEGOTIATE_GSS_CODE && !gss_done) { char GSSok =3D 'N'; + #ifdef ENABLE_GSS /* No GSSAPI encryption when on Unix socket */ if (!IS_AF_UNIX(port->laddr.addr.ss_family)) @@ -2531,37 +2532,19 @@ ConnCreate(int serverFd) return NULL; } = - /* - * Allocate GSSAPI specific state struct - */ -#ifndef EXEC_BACKEND -#if defined(ENABLE_GSS) || defined(ENABLE_SSPI) - port->gss =3D (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; } = = /* * 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 ConnFree(Port *conn) { -#ifdef USE_SSL - secure_close(conn); -#endif - if (conn->gss) - free(conn->gss); free(conn); } = @@ -4917,18 +4900,6 @@ SubPostmasterMain(int argc, char *argv[]) /* Setup as postmaster child */ InitPostmasterChild(); = - /* - * Set up memory area for GSS information. Mirrors the code in ConnCreat= e - * for the non-exec case. - */ -#if defined(ENABLE_GSS) || defined(ENABLE_SSPI) - port.gss =3D (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 wan= t * to do this before going any further to ensure that we can attach at t= he diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/po= stinit.c index 82d451569d..9723e457ce 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -270,11 +270,22 @@ PerformAuthentication(Port *port) be_tls_get_compression(port) ? _("on") : _("off")); #endif #ifdef ENABLE_GSS - if (be_gssapi_get_princ(port)) - appendStringInfo(&logmsg, _(" GSS (authenticated=3D%s, encrypted=3D%s,= principal=3D%s)"), - be_gssapi_get_auth(port) ? _("yes") : _("no"), - be_gssapi_get_enc(port) ? _("yes") : _("no"), - be_gssapi_get_princ(port)); + if (port->gss) + { + const char *princ =3D be_gssapi_get_princ(port); + + if (princ) + appendStringInfo(&logmsg, + _(" GSS (authenticated=3D%s, encrypted=3D%s, principal=3D%s)"), + be_gssapi_get_auth(port) ? _("yes") : _("no"), + be_gssapi_get_enc(port) ? _("yes") : _("no"), + princ); + else + appendStringInfo(&logmsg, + _(" GSS (authenticated=3D%s, encrypted=3D%s)"), + be_gssapi_get_auth(port) ? _("yes") : _("no"), + be_gssapi_get_enc(port) ? _("yes") : _("no")); + } #endif = ereport(LOG, errmsg_internal("%s", logmsg.data)); diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 0a23281ad5..fa778e1192 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -176,8 +176,9 @@ typedef struct Port #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) = /* - * If GSSAPI is supported, store GSSAPI information. Otherwise, store a - * NULL pointer to make sure offsets in the struct remain the same. + * If GSSAPI is supported and used on this connection, store GSSAPI + * information. Even when GSSAPI is not compiled in, store a NULL point= er + * to keep struct offsets the same (for extension ABI compatibility). */ pg_gssinfo *gss; #else ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="0003-remove-authtype-restriction.patch"; charset="us-ascii" Content-ID: <1528789.1609108300.4@sss.pgh.pa.us> Content-Description: 0003-remove-authtype-restriction.patch Content-Transfer-Encoding: quoted-printable diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 7f65310c14..aad7f982bf 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -382,17 +382,6 @@ ClientAuthentication(Port *port) errmsg("connection requires a valid client certificate"))); } = -#ifdef ENABLE_GSS - if (port->gss && port->gss->enc && port->hba->auth_method !=3D uaReject = && - port->hba->auth_method !=3D uaImplicitReject && - port->hba->auth_method !=3D uaTrust && - port->hba->auth_method !=3D uaGSS) - { - ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), - errmsg("GSSAPI encryption can only be used with gss, trust, or reje= ct authentication methods"))); - } -#endif - /* * Now proceed to do the actual authentication check */ diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 41ca0791ea..99319b273a 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1447,19 +1447,6 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg =3D "gssapi authentication is not supported on local sockets"; return NULL; } - if (parsedline->conntype =3D=3D ctHostGSS && - parsedline->auth_method !=3D uaGSS && - parsedline->auth_method !=3D uaReject && - parsedline->auth_method !=3D uaTrust) - { - ereport(elevel, - (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("GSSAPI encryption only supports gss, trust, or reject authen= tication"), - errcontext("line %d of configuration file \"%s\"", - line_num, HbaFileName))); - *err_msg =3D "GSSAPI encryption only supports gss, trust, or reject aut= hentication"; - return NULL; - } = if (parsedline->conntype !=3D ctLocal && parsedline->auth_method =3D=3D uaPeer) ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="0004-gss-documentation-fixes.patch"; charset="us-ascii" Content-ID: <1528789.1609108300.5@sss.pgh.pa.us> Content-Description: 0004-gss-documentation-fixes.patch Content-Transfer-Encoding: quoted-printable diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index a0a9ac9eed..9a5c9318ce 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -198,14 +198,6 @@ hostnogssenc database userhostgssenc record is ignored except for log= ging a warning that it cannot match any connections. - - - Note that the only supported - authentication methods for = use - with GSSAPI encryption - are gss, reject, - and trust. - = @@ -1201,14 +1193,13 @@ omicron bryanh guest1 GSSAPI is an industry-standard protocol for secure authentication defined in RFC 2743. - PostgreSQL - supports GSSAPI for use as either an encry= pted, - authenticated layer, or for authentication only. + supports GSSAPI for authentication, + communications encryption, or both. GSSAPI provides automatic authentication (single sign-on) for systems that support it. The authentication itse= lf is secure. If GSSAPI encryption - (see hostgssenc) or SSL encrypt= ion are + or SSL encryption is used, the data sent along the database connection will be encrypted; otherwise, it will not. @@ -1220,37 +1211,46 @@ omicron bryanh guest1 = When GSSAPI uses - Kerberos, it uses a standard principal - in the format + Kerberos, it uses a standard service + principal (authentication identity) name in the format servicename/hostname= @realm. - The PostgreSQL server will accept any principal that is included in t= he keytab used by - the server, but care needs to be taken to specify the correct princip= al details when - making the connection from the client using the krbsrvname connection parameter. (See - also .) The installation defau= lt can be - changed from the default postgres at build time us= ing - ./configure --with-krb-srvnam=3Dwhate= ver. - 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 (POSTGRES). - - - hostname is the fully qualified host name = of the - server machine. The service principal's realm is the preferred realm - of the server machine. - - - - Client principals can be mapped to different PostgreSQL<= /productname> - database user names with pg_ident.conf. For exa= mple, + The principal name used by a particular installation is not encoded i= n + the PostgreSQL server in any way; rather i= t + is specified in the keytab file that the serve= r + reads to determine its identity. If multiple principals are listed i= n + the keytab file, the server will accept any one of them. + The server's realm name is the preferred realm specified in the Kerbe= ros + configuration file(s) accessible to the server. + + + + When connecting, the client must know the principal name of the serve= r + it intends to connect to. The servicename + part of the principal is ordinarily postgres, + but another value can be selected via libpq's + connection parameter. + The hostname part is the fully qualified + host name that libpq is told to connect to= . + The realm name is the preferred realm specified in the Kerberos + configuration file(s) accessible to the client. + + + + The client will also have a principal name for its own identity + (and it must have a valid ticket for this principal). To + use GSSAPI for authentication, the client + principal must be associated with + a PostgreSQL database user name. + The pg_ident.conf configuration file can be used + to map principals to user names; for example, pgusername@realm could be mapped to just = pgusername. Alternatively, you can use the full username@realm= principal as the role name in PostgreSQL without any ma= pping. = - PostgreSQL also supports a parameter to st= rip the realm from + PostgreSQL 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 strongly discouraged as it is then impossible to distinguish differen= t users with the same user name but coming from different realms. To enable = this, @@ -1264,39 +1264,34 @@ omicron bryanh guest1 = - Make sure that your server keytab file is readable (and preferably - only readable, not writable) by the PostgreSQL - server account. (See also .) The lo= cation - of the key file is specified by the configuration parameter. The default is - /usr/local/pgsql/etc/krb5.keytab (or whatever - directory was specified as sysconfdir at build tim= e). + FILE:/usr/local/pgsql/etc/krb5.keytab + (where the directory part is whatever was specified + as sysconfdir at build time). For security reasons, it is recommended to use a separate keytab just for the PostgreSQL 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 PostgreSQL + server account. (See also .) + - The keytab file is generated by the Kerberos software; see the - Kerberos documentation for details. The following example is - for MIT-compatible Kerberos 5 implementations: + The keytab file is generated using the Kerberos software; see the + Kerberos documentation for details. The following example shows + doing this using the kadmin tool of + MIT-compatible Kerberos 5 implementations: -kadmin% ank -randkey postgres/server.my.domai= n.org +kadmin% addprinc -randkey postgres/server.my.= domain.org kadmin% ktadd -k krb5.keytab postgres/server.= my.domain.org = - When connecting to the database make sure you have a ticket for a - principal matching the requested database user name. For example, for - database user name fred, principal - fred@EXAMPLE.COM would be able to connect. To also= allow - principal fred/users.example.com@EXAMPLE.COM, use = a user name - map, as described in . - - - - The following configuration options are supported for GS= SAPI: + The following authentication options are supported for + the GSSAPI authentication method: include_realm @@ -1319,7 +1314,7 @@ omicron bryanh guest1 map - Allows for mapping between system and database user names. See + Allows mapping from client principals to database user names. See for details. For a GSSAPI= /Kerberos principal, such as username@EXAMPLE.COM (or, l= ess commonly, username/hostbased@EXAMPLE.COM), the @@ -1346,6 +1341,15 @@ omicron bryanh guest1 + + + In addition to these settings, which can be different for + different pg_hba.conf entries, there is the + server-wide configuration + parameter. If that is set to true, client principals are matched to + user map entries case-insensitively. krb_realm, i= f + set, is also matched case-insensitively. + = diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 67c5d4c36b..a5db58d468 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1581,6 +1581,16 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname connection.SSLwith libpq + + + Note that if GSSAPI encryption is possible, + that will be used in preference to SSL + encryption, regardless of the value of sslmode= . + To force use of SSL encryption in an + environment that has working GSSAPI + infrastructure (such as a Kerberos server), also + set gssencmode to disable. + = @@ -1784,6 +1794,15 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname This must match the service name specified in the server configuration for Kerberos authentication to succeed. (See also .) + The default value is normally postgres, + but that can be changed when + building PostgreSQL via + the option + of configure. + In most environments, this parameter never needs to be changed. + Some Kerberos implementations might require a different service n= ame, + such as Microsoft Active Directory which requires the service nam= e + to be in upper case (POSTGRES). diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 4899bacda7..98b42bb269 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1454,6 +1454,10 @@ SELCT 1/0; SSL-encrypted. To continue after N, send the usual StartupMessage and proceed witho= ut encryption. + (Alternatively, it is permissible to issue a GSSENCRequest message + after an N response to try to + use GSSAPI encryption instead + of SSL.) = @@ -1509,20 +1513,14 @@ SELCT 1/0; result from the server, until it returns no output. When sending the results of gss_init_sec_context() to the server, prepend the length of the message as a four byte integer in network b= yte - order. If this is successful, then use gss_wrap() to - encrypt the usual StartupMessage and all subsequent data, prepending = the - length of the result from gss_wrap() as a four b= yte - integer in network byte order to the actual encrypted payload. Note = that - the server will only accept encrypted packets from the client which a= re less - than 16kB; gss_wrap_size_limit() should be used = by the - client to determine the size of the unencrypted message which will fi= t - within this limit and larger messages should be broken up into multip= le - gss_wrap() calls. Typical segments are 8kB of - unencrypted data, resulting in encrypted packets of slightly larger t= han 8kB - 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 aft= er + order. + To continue after N, send the usual StartupMessage and proceed witho= ut encryption. + (Alternatively, it is permissible to issue an SSLRequest message + after an N response to try to + use SSL encryption instead + of GSSAPI.) = @@ -1532,9 +1530,7 @@ SELCT 1/0; support to PostgreSQL. In this case the connection must be closed, but the frontend might choose to open a fr= esh connection and proceed without requesting GSSAPI - encryption. Given the length limits specified above, the ErrorMessag= e can - not be confused with a proper response from the server with an approp= riate - length. + encryption. = @@ -1542,6 +1538,22 @@ SELCT 1/0; opened to send a CancelRequest message. = + + Once GSSAPI encryption has been successfully + established, use gss_wrap() to + encrypt the usual StartupMessage and all subsequent data, prepending = the + length of the result from gss_wrap() as a four b= yte + integer in network byte order to the actual encrypted payload. Note = that + the server will only accept encrypted packets from the client which a= re less + than 16kB; gss_wrap_size_limit() should be used = by the + client to determine the size of the unencrypted message which will fi= t + within this limit and larger messages should be broken up into multip= le + gss_wrap() calls. Typical segments are 8kB of + unencrypted data, resulting in encrypted packets of slightly larger t= han 8kB + but well within the 16kB maximum. The server can be expected to not = send + encrypted packets of larger than 16kB to the client. + + While the protocol itself does not provide a way for the server to force GSSAPI encryption, the administrator can diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 17e938148c..283352d3a4 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -2563,7 +2563,7 @@ openssl x509 -req -in server.csr -text -days 365 \ PostgreSQL also has native support for using GSSAPI to encrypt client/server communication= s for increased security. Support requires that a GSSAPI - implementation (such as MIT krb5) is installed on both client and serv= er + implementation (such as MIT Kerberos) is installed on both client and = server systems, and that support in PostgreSQL is enabled at build time (see ). @@ -2582,6 +2582,16 @@ openssl x509 -req -in server.csr -text -days 365 \ some or all connections. = + + When using GSSAPI for encryption, it is common to + use GSSAPI for authentication as well, since the + underlying mechanism will determine both client and server identities + (according to the GSSAPI implementation) in any + case. But this is not required; + another PostgreSQL authentication method + can be chosen to perform additional verification. + + Other than configuration of the negotiation behavior, GSSAPI encryption requires no setup beyo= nd ------- =_aaaaaaaaaa0--