Direct SSL connection with ALPN and HBA rules

Started by Michael Paquierover 1 year ago59 messages
#1Michael Paquier
michael@paquier.xyz

Hi all,
(Heikki in CC.)

Since 91044ae4baea (require ALPN for direct SSL connections) and
d39a49c1e459 (direct hanshake), direct SSL connections are supported
(yeah!), still the thread where this has been discussed does not cover
the potential impact on HBA rules:
/messages/by-id/CAM-w4HOEAzxyY01ZKOj-iq=M4-VDk=vzQgUsuqiTFjFDZaebdg@mail.gmail.com

My point is, would there be a point in being able to enforce that ALPN
is used from the server rather than just relying on the client-side
sslnegotiation to decide if direct SSL connections should be forced or
not?

Hence, I'd imagine that we could have an HBA option for hostssl rules,
like a negotiation={direct,postgres,all} that cross-checks
Port->alpn_used with the option value in a hostssl entry, rejecting
the use of connections using direct connections or the default
protocol if these are not used, giving users a way to avoid one. As
this is a new thing, there may be an argument in this option for
security reasons, as well, so as it would be possible for operators to
turn that off in the server.

Thoughts or comments?
--
Michael

#2Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Michael Paquier (#1)
Re: Direct SSL connection with ALPN and HBA rules

On 19/04/2024 08:06, Michael Paquier wrote:

Hi all,
(Heikki in CC.)

(Adding Jacob)

Since 91044ae4baea (require ALPN for direct SSL connections) and
d39a49c1e459 (direct hanshake), direct SSL connections are supported
(yeah!), still the thread where this has been discussed does not cover
the potential impact on HBA rules:
/messages/by-id/CAM-w4HOEAzxyY01ZKOj-iq=M4-VDk=vzQgUsuqiTFjFDZaebdg@mail.gmail.com

My point is, would there be a point in being able to enforce that ALPN
is used from the server rather than just relying on the client-side
sslnegotiation to decide if direct SSL connections should be forced or
not?

Hence, I'd imagine that we could have an HBA option for hostssl rules,
like a negotiation={direct,postgres,all} that cross-checks
Port->alpn_used with the option value in a hostssl entry, rejecting
the use of connections using direct connections or the default
protocol if these are not used, giving users a way to avoid one. As
this is a new thing, there may be an argument in this option for
security reasons, as well, so as it would be possible for operators to
turn that off in the server.

I don't think ALPN gives any meaningful security benefit, when used with
the traditional 'postgres' SSL negotiation. There's little possibility
of mixing that up with some other protocol, so I don't see any need to
enforce it from server side. This was briefly discussed on that original
thread [1]/messages/by-id/CAAWbhmjetCVgu9pHJFkQ4ejuXuaz2mD1oniXokRHft0usCa7Yg@mail.gmail.com. With direct SSL negotiation, we always require ALPN.

I don't see direct SSL negotiation as a security feature. Rather, the
point is to reduce connection latency by saving one round-trip. For
example, if gssencmode=prefer, but the server refuses GSS encryption, it
seems fine to continue with negotiated SSL, instead of establishing a
new connection with direct SSL. What would be the use case of requiring
direct SSL in the server? What extra security do you get?

Controlling these in HBA is a bit inconvenient, because you only find
out after authentication if it's allowed or not. So if e.g. direct SSL
connections are disabled for a user, the client would still establish a
direct SSL connection, send the startup packet, and only then get
rejected. The client would not know if it was rejected because of the
direct SSL or for some reason, so it needs to retry with negotiated SSL.
Currently, as it is master, if the TLS layer is established with direct
SSL, you never need to retry with traditional negotiation, or vice versa.

[1]: /messages/by-id/CAAWbhmjetCVgu9pHJFkQ4ejuXuaz2mD1oniXokRHft0usCa7Yg@mail.gmail.com
/messages/by-id/CAAWbhmjetCVgu9pHJFkQ4ejuXuaz2mD1oniXokRHft0usCa7Yg@mail.gmail.com

--
Heikki Linnakangas
Neon (https://neon.tech)

#3Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Heikki Linnakangas (#2)
Re: Direct SSL connection with ALPN and HBA rules

On Fri, Apr 19, 2024 at 6:56 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 19/04/2024 08:06, Michael Paquier wrote:

Since 91044ae4baea (require ALPN for direct SSL connections) and
d39a49c1e459 (direct hanshake), direct SSL connections are supported
(yeah!), still the thread where this has been discussed does not cover
the potential impact on HBA rules:
/messages/by-id/CAM-w4HOEAzxyY01ZKOj-iq=M4-VDk=vzQgUsuqiTFjFDZaebdg@mail.gmail.com

My point is, would there be a point in being able to enforce that ALPN
is used from the server rather than just relying on the client-side
sslnegotiation to decide if direct SSL connections should be forced or
not?

I'm a little confused about whether we're talking about requiring ALPN
or requiring direct connections. I think you mean the latter, Michael?

Personally, I was hoping that we'd have a postgresql.conf option to
reject every network connection that wasn't direct SSL, but I ran out
of time to review the patchset for 17. I would like to see server-side
enforcement of direct SSL in some way, eventually. I hadn't given much
thought to HBA, though.

I don't think ALPN gives any meaningful security benefit, when used with
the traditional 'postgres' SSL negotiation. There's little possibility
of mixing that up with some other protocol, so I don't see any need to
enforce it from server side. This was briefly discussed on that original
thread [1].

Agreed. By the time you've issued a traditional SSL startup packet,
and the server responds with a go-ahead, it's pretty much understood
what protocol is in use.

With direct SSL negotiation, we always require ALPN.

(As an aside: I haven't gotten to test the version of the patch that
made it into 17 yet, but from a quick glance it looks like we're not
rejecting mismatched ALPN during the handshake as noted in [1]/messages/by-id/CAOYmi+=cnV-8V8TndSkEF6Htqa7qHQUL_KnQU8-DrT0Jjnm3_Q@mail.gmail.com.)

I don't see direct SSL negotiation as a security feature.

`direct` mode is not, since it's opportunistic, but I think people are
going to use `requiredirect` as a security feature. At least, I was
hoping to do that myself...

Rather, the
point is to reduce connection latency by saving one round-trip. For
example, if gssencmode=prefer, but the server refuses GSS encryption, it
seems fine to continue with negotiated SSL, instead of establishing a
new connection with direct SSL.

Well, assuming the user is okay with plaintext negotiation at all.
(Was that fixed before the patch went in? Is GSS negotiation still
allowed even with requiredirect?)

What would be the use case of requiring
direct SSL in the server? What extra security do you get?

You get protection against attacks that could have otherwise happened
during the plaintext portion of the handshake. That has architectural
implications for more advanced uses of SCRAM, and it should prevent
any repeats of CVE-2021-23222/23214. And if the peer doesn't pass the
TLS handshake, they can't send you anything that you might forget is
untrusted (like, say, an error message).

Controlling these in HBA is a bit inconvenient, because you only find
out after authentication if it's allowed or not. So if e.g. direct SSL
connections are disabled for a user,

Hopefully disabling direct SSL piecemeal is not a desired use case?
I'm not sure it makes sense to focus on that. Forcing it to be enabled
shouldn't have the same problem, should it?

--Jacob

[1]: /messages/by-id/CAOYmi+=cnV-8V8TndSkEF6Htqa7qHQUL_KnQU8-DrT0Jjnm3_Q@mail.gmail.com

#4Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Jacob Champion (#3)
Re: Direct SSL connection with ALPN and HBA rules

On 19/04/2024 19:48, Jacob Champion wrote:

On Fri, Apr 19, 2024 at 6:56 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

With direct SSL negotiation, we always require ALPN.

(As an aside: I haven't gotten to test the version of the patch that
made it into 17 yet, but from a quick glance it looks like we're not
rejecting mismatched ALPN during the handshake as noted in [1].)

Ah, good catch, that fell through the cracks. Agreed, the client should
reject a direct SSL connection if the server didn't send ALPN. I'll add
that to the Open Items so we don't forget again.

I don't see direct SSL negotiation as a security feature.

`direct` mode is not, since it's opportunistic, but I think people are
going to use `requiredirect` as a security feature. At least, I was
hoping to do that myself...

Rather, the
point is to reduce connection latency by saving one round-trip. For
example, if gssencmode=prefer, but the server refuses GSS encryption, it
seems fine to continue with negotiated SSL, instead of establishing a
new connection with direct SSL.

Well, assuming the user is okay with plaintext negotiation at all.
(Was that fixed before the patch went in? Is GSS negotiation still
allowed even with requiredirect?)

To disable sending the startup packet in plaintext, you need to use
sslmode=require. Same as before the patch. GSS is still allowed, as it
takes precedence over SSL if both are enabled in libpq. Per the docs:

Note that if gssencmode is set to prefer, a GSS connection is
attempted first. If the server rejects GSS encryption, SSL is
negotiated over the same TCP connection using the traditional
postgres protocol, regardless of sslnegotiation. In other words, the
direct SSL handshake is not used, if a TCP connection has already
been established and can be used for the SSL handshake.

What would be the use case of requiring
direct SSL in the server? What extra security do you get?

You get protection against attacks that could have otherwise happened
during the plaintext portion of the handshake. That has architectural
implications for more advanced uses of SCRAM, and it should prevent
any repeats of CVE-2021-23222/23214. And if the peer doesn't pass the
TLS handshake, they can't send you anything that you might forget is
untrusted (like, say, an error message).

Can you elaborate on the more advanced uses of SCRAM?

Controlling these in HBA is a bit inconvenient, because you only find
out after authentication if it's allowed or not. So if e.g. direct SSL
connections are disabled for a user,

Hopefully disabling direct SSL piecemeal is not a desired use case?
I'm not sure it makes sense to focus on that. Forcing it to be enabled
shouldn't have the same problem, should it?

Forcing it to be enabled piecemeal based on role or database has similar
problems. Forcing it enabled for all connections seems sensible, though.
Forcing it enabled based on the client's source IP address, but not
user/database would be somewhat sensible too, but we don't currently
have the HBA code to check the source IP and accept/reject SSLRequest
based on that. The HBA rejection always happens after the client has
sent the startup packet.

--
Heikki Linnakangas
Neon (https://neon.tech)

#5Michael Paquier
michael@paquier.xyz
In reply to: Heikki Linnakangas (#4)
Re: Direct SSL connection with ALPN and HBA rules

On Sat, Apr 20, 2024 at 12:43:24AM +0300, Heikki Linnakangas wrote:

On 19/04/2024 19:48, Jacob Champion wrote:

On Fri, Apr 19, 2024 at 6:56 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

With direct SSL negotiation, we always require ALPN.

(As an aside: I haven't gotten to test the version of the patch that
made it into 17 yet, but from a quick glance it looks like we're not
rejecting mismatched ALPN during the handshake as noted in [1].)

Ah, good catch, that fell through the cracks. Agreed, the client should
reject a direct SSL connection if the server didn't send ALPN. I'll add that
to the Open Items so we don't forget again.

Would somebody like to write a patch for that? I'm planning to look
at this code more closely, as well.

You get protection against attacks that could have otherwise happened
during the plaintext portion of the handshake. That has architectural
implications for more advanced uses of SCRAM, and it should prevent
any repeats of CVE-2021-23222/23214. And if the peer doesn't pass the
TLS handshake, they can't send you anything that you might forget is
untrusted (like, say, an error message).

Can you elaborate on the more advanced uses of SCRAM?

I'm not sure what you mean here, either, Jacob.

Controlling these in HBA is a bit inconvenient, because you only find
out after authentication if it's allowed or not. So if e.g. direct SSL
connections are disabled for a user,

Hopefully disabling direct SSL piecemeal is not a desired use case?
I'm not sure it makes sense to focus on that. Forcing it to be enabled
shouldn't have the same problem, should it?

I'd get behind the case where a server rejects everything except
direct SSL, yeah. Sticking that into a format similar to HBA rules
would easily give the flexibility to be able to accept or reject
direct or default SSL, though, while making it easy to parse. The
implementation is not really complicated, and not far from the
existing hostssl and nohostssl.

As a whole, I can get behind a unique GUC that forces the use of
direct. Or, we could extend the existing "ssl" GUC with a new
"direct" value to accept only direct connections and restrict the
original protocol (and a new "postgres" for the pre-16 protocol,
rejecting direct?), while "on" is able to accept both.

Forcing it to be enabled piecemeal based on role or database has similar
problems. Forcing it enabled for all connections seems sensible, though.
Forcing it enabled based on the client's source IP address, but not
user/database would be somewhat sensible too, but we don't currently have
the HBA code to check the source IP and accept/reject SSLRequest based on
that. The HBA rejection always happens after the client has sent the startup
packet.

Hmm. Splitting the logic checking HBA entries (around check_hba) so
as we'd check for a portion of its contents depending on what the
server has received or not from the client would not be that
complicated. I'd question whether it makes sense to mix this
information within the same configuration files as the ones holding
the current HBA rules. If the same rules are used for the
pre-startup-packet phase and the post-startup-packet phase, we'd want
new keywords for these HBA rules, something different than the
existing sslmode and no sslmode?
--
Michael

#6Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Michael Paquier (#5)
Re: Direct SSL connection with ALPN and HBA rules

On 22/04/2024 10:19, Michael Paquier wrote:

On Sat, Apr 20, 2024 at 12:43:24AM +0300, Heikki Linnakangas wrote:

On 19/04/2024 19:48, Jacob Champion wrote:

On Fri, Apr 19, 2024 at 6:56 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

With direct SSL negotiation, we always require ALPN.

(As an aside: I haven't gotten to test the version of the patch that
made it into 17 yet, but from a quick glance it looks like we're not
rejecting mismatched ALPN during the handshake as noted in [1].)

Ah, good catch, that fell through the cracks. Agreed, the client should
reject a direct SSL connection if the server didn't send ALPN. I'll add that
to the Open Items so we don't forget again.

Would somebody like to write a patch for that? I'm planning to look
at this code more closely, as well.

I plan to write the patch later today.

Controlling these in HBA is a bit inconvenient, because you only find
out after authentication if it's allowed or not. So if e.g. direct SSL
connections are disabled for a user,

Hopefully disabling direct SSL piecemeal is not a desired use case?
I'm not sure it makes sense to focus on that. Forcing it to be enabled
shouldn't have the same problem, should it?

I'd get behind the case where a server rejects everything except
direct SSL, yeah. Sticking that into a format similar to HBA rules
would easily give the flexibility to be able to accept or reject
direct or default SSL, though, while making it easy to parse. The
implementation is not really complicated, and not far from the
existing hostssl and nohostssl.

As a whole, I can get behind a unique GUC that forces the use of
direct. Or, we could extend the existing "ssl" GUC with a new
"direct" value to accept only direct connections and restrict the
original protocol (and a new "postgres" for the pre-16 protocol,
rejecting direct?), while "on" is able to accept both.

I'd be OK with that, although I still don't really see the point of
forcing this from the server side. We could also add this later.

Forcing it to be enabled piecemeal based on role or database has similar
problems. Forcing it enabled for all connections seems sensible, though.
Forcing it enabled based on the client's source IP address, but not
user/database would be somewhat sensible too, but we don't currently have
the HBA code to check the source IP and accept/reject SSLRequest based on
that. The HBA rejection always happens after the client has sent the startup
packet.

Hmm. Splitting the logic checking HBA entries (around check_hba) so
as we'd check for a portion of its contents depending on what the
server has received or not from the client would not be that
complicated. I'd question whether it makes sense to mix this
information within the same configuration files as the ones holding
the current HBA rules. If the same rules are used for the
pre-startup-packet phase and the post-startup-packet phase, we'd want
new keywords for these HBA rules, something different than the
existing sslmode and no sslmode?

Sounds complicated, and I don't really see the use case for controlling
the direct SSL support in such a fine-grained fashion.

It would be nice if we could reject non-SSL connections before the
client sends the startup packet, but that's not possible because in a
plaintext connection, that's the first packet that the client sends. The
reverse would be possible: reject SSLRequest or direct SSL connection
immediately, if HBA doesn't allow non-SSL connections from that IP
address. But that's not very interesting.

HBA-based control would certainly be v18 material.

--
Heikki Linnakangas
Neon (https://neon.tech)

#7Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Heikki Linnakangas (#6)
1 attachment(s)
Re: Direct SSL connection with ALPN and HBA rules

On 22/04/2024 10:47, Heikki Linnakangas wrote:

On 22/04/2024 10:19, Michael Paquier wrote:

On Sat, Apr 20, 2024 at 12:43:24AM +0300, Heikki Linnakangas wrote:

On 19/04/2024 19:48, Jacob Champion wrote:

On Fri, Apr 19, 2024 at 6:56 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

With direct SSL negotiation, we always require ALPN.

(As an aside: I haven't gotten to test the version of the patch that
made it into 17 yet, but from a quick glance it looks like we're not
rejecting mismatched ALPN during the handshake as noted in [1].)

Ah, good catch, that fell through the cracks. Agreed, the client should
reject a direct SSL connection if the server didn't send ALPN. I'll add that
to the Open Items so we don't forget again.

Would somebody like to write a patch for that? I'm planning to look
at this code more closely, as well.

I plan to write the patch later today.

Here's the patch for that. The error message is:

"direct SSL connection was established without ALPN protocol negotiation
extension"

That's accurate, but I wonder if we could make it more useful to a user
who's wondering what went wrong. I'd imagine that if the server doesn't
support ALPN, it's because you have some kind of a (not necessarily
malicious) generic SSL man-in-the-middle that doesn't support it. Or
you're trying to connect to an HTTPS server. Suggestions welcome.

--
Heikki Linnakangas
Neon (https://neon.tech)

Attachments:

require-alpn-in-direct-mode.patchtext/x-patch; charset=UTF-8; name=require-alpn-in-direct-mode.patchDownload
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index ec20e3f3a9..f9156e29ca 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -3524,6 +3524,13 @@ keep_going:						/* We will come back to here until there is
 				pollres = pqsecure_open_client(conn);
 				if (pollres == PGRES_POLLING_OK)
 				{
+					/* ALPN is mandatory with direct SSL negotiation */
+					if (conn->current_enc_method == ENC_DIRECT_SSL && !conn->ssl_alpn_used)
+					{
+						libpq_append_conn_error(conn, "direct SSL connection was established without ALPN protocol negotiation extension");
+						CONNECTION_FAILED();
+					}
+
 					/*
 					 * At this point we should have no data already buffered.
 					 * If we do, it was received before we performed the SSL
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index e7a4d006e1..8df3c751db 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -1585,6 +1585,31 @@ open_client_SSL(PGconn *conn)
 		}
 	}
 
+	/* Get the protocol selected by ALPN */
+	{
+		const unsigned char *selected;
+		unsigned int len;
+
+		SSL_get0_alpn_selected(conn->ssl, &selected, &len);
+
+		/* If ALPN is used, check that we negotiated the expected protocol */
+		if (selected != NULL)
+		{
+			if (len == strlen(PG_ALPN_PROTOCOL) &&
+				memcmp(selected, PG_ALPN_PROTOCOL, strlen(PG_ALPN_PROTOCOL)) == 0)
+			{
+				conn->ssl_alpn_used = true;
+			}
+			else
+			{
+				/* shouldn't happen */
+				libpq_append_conn_error(conn, "SSL connection was established with unexpected ALPN protocol");
+				pgtls_close(conn);
+				return PGRES_POLLING_FAILED;
+			}
+		}
+	}
+
 	/*
 	 * We already checked the server certificate in initialize_SSL() using
 	 * SSL_CTX_set_verify(), if root.crt exists.
@@ -1632,6 +1657,7 @@ pgtls_close(PGconn *conn)
 			conn->ssl = NULL;
 			conn->ssl_in_use = false;
 			conn->ssl_handshake_started = false;
+			conn->ssl_alpn_used = false;
 
 			destroy_needed = true;
 		}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 3691e5ee96..a6792917cf 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -569,6 +569,7 @@ struct pg_conn
 	bool		ssl_handshake_started;
 	bool		ssl_cert_requested; /* Did the server ask us for a cert? */
 	bool		ssl_cert_sent;	/* Did we send one in reply? */
+	bool		ssl_alpn_used;	/* Did we negotiate a protocol with TLS ALPN? */
 
 #ifdef USE_SSL
 #ifdef USE_OPENSSL
#8Michael Paquier
michael@paquier.xyz
In reply to: Heikki Linnakangas (#6)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, Apr 22, 2024 at 10:47:51AM +0300, Heikki Linnakangas wrote:

On 22/04/2024 10:19, Michael Paquier wrote:

As a whole, I can get behind a unique GUC that forces the use of
direct. Or, we could extend the existing "ssl" GUC with a new
"direct" value to accept only direct connections and restrict the
original protocol (and a new "postgres" for the pre-16 protocol,
rejecting direct?), while "on" is able to accept both.

I'd be OK with that, although I still don't really see the point of forcing
this from the server side. We could also add this later.

I'd be OK with doing something only in v18, if need be. Jacob, what
do you think?

Hmm. Splitting the logic checking HBA entries (around check_hba) so
as we'd check for a portion of its contents depending on what the
server has received or not from the client would not be that
complicated. I'd question whether it makes sense to mix this
information within the same configuration files as the ones holding
the current HBA rules. If the same rules are used for the
pre-startup-packet phase and the post-startup-packet phase, we'd want
new keywords for these HBA rules, something different than the
existing sslmode and no sslmode?

Sounds complicated, and I don't really see the use case for controlling the
direct SSL support in such a fine-grained fashion.

It would be nice if we could reject non-SSL connections before the client
sends the startup packet, but that's not possible because in a plaintext
connection, that's the first packet that the client sends. The reverse would
be possible: reject SSLRequest or direct SSL connection immediately, if HBA
doesn't allow non-SSL connections from that IP address. But that's not very
interesting.

I'm not completely sure, actually. We have the APIs to do that in
simple ways with existing keywords and new options. And there is some
merit being able to have more complex connection policies. If both of
you object to that, I won't insist.

HBA-based control would certainly be v18 material.

Surely.
--
Michael

#9Michael Paquier
michael@paquier.xyz
In reply to: Heikki Linnakangas (#7)
Re: Direct SSL connection with ALPN and HBA rules

On Tue, Apr 23, 2024 at 01:48:04AM +0300, Heikki Linnakangas wrote:

Here's the patch for that. The error message is:

"direct SSL connection was established without ALPN protocol negotiation
extension"

WFM.

That's accurate, but I wonder if we could make it more useful to a user
who's wondering what went wrong. I'd imagine that if the server doesn't
support ALPN, it's because you have some kind of a (not necessarily
malicious) generic SSL man-in-the-middle that doesn't support it. Or you're
trying to connect to an HTTPS server. Suggestions welcome.

Hmm. Is there any point in calling SSL_get0_alpn_selected() in
open_client_SSL() to get the ALPN if current_enc_method is not
ENC_DIRECT_SSL?

In the documentation of PQsslAttribute(), it is mentioned that empty
string is returned for "alpn" if ALPN was not used, however the code
returns NULL in this case:
SSL_get0_alpn_selected(conn->ssl, &data, &len);
if (data == NULL || len == 0 || len > sizeof(alpn_str) - 1)
return NULL;
--
Michael

#10Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Heikki Linnakangas (#4)
Re: Direct SSL connection with ALPN and HBA rules

On Fri, Apr 19, 2024 at 2:43 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 19/04/2024 19:48, Jacob Champion wrote:

On Fri, Apr 19, 2024 at 6:56 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

With direct SSL negotiation, we always require ALPN.

(As an aside: I haven't gotten to test the version of the patch that
made it into 17 yet, but from a quick glance it looks like we're not
rejecting mismatched ALPN during the handshake as noted in [1].)

Ah, good catch, that fell through the cracks. Agreed, the client should
reject a direct SSL connection if the server didn't send ALPN. I'll add
that to the Open Items so we don't forget again.

Yes, the client should also reject, but that's not what I'm referring
to above. The server needs to fail the TLS handshake itself with the
proper error code (I think it's `no_application_protocol`?); otherwise
a client implementing a different protocol could consume the
application-level bytes coming back from the server and act on them.
That's the protocol confusion attack from ALPACA we're trying to
avoid.

Well, assuming the user is okay with plaintext negotiation at all.
(Was that fixed before the patch went in? Is GSS negotiation still
allowed even with requiredirect?)

To disable sending the startup packet in plaintext, you need to use
sslmode=require. Same as before the patch. GSS is still allowed, as it
takes precedence over SSL if both are enabled in libpq. Per the docs:

Note that if gssencmode is set to prefer, a GSS connection is
attempted first. If the server rejects GSS encryption, SSL is
negotiated over the same TCP connection using the traditional
postgres protocol, regardless of sslnegotiation. In other words, the
direct SSL handshake is not used, if a TCP connection has already
been established and can be used for the SSL handshake.

Oh. That's actually disappointing, since gssencmode=prefer is the
default. A question I had in the original thread was, what's the
rationale behind a "require direct ssl" option that doesn't actually
require it?

What would be the use case of requiring
direct SSL in the server? What extra security do you get?

You get protection against attacks that could have otherwise happened
during the plaintext portion of the handshake. That has architectural
implications for more advanced uses of SCRAM, and it should prevent
any repeats of CVE-2021-23222/23214. And if the peer doesn't pass the
TLS handshake, they can't send you anything that you might forget is
untrusted (like, say, an error message).

Can you elaborate on the more advanced uses of SCRAM?

If you're using SCRAM to authenticate the server, as opposed to just a
really strong password auth, then it really helps an analysis of the
security to know that there are no plaintext bytes that have been
interpreted by the client. This came up briefly in the conversations
that led to commit d0f4824a.

To be fair, it's a more academic concern at the moment; my imagination
can only come up with problems for SCRAM-based TLS that would also be
vulnerabilities for standard certificate-based TLS. But whether or not
it's an advantage for the code today is also kind of orthogonal to my
point. The security argument of direct SSL mode is that it reduces
risk for the system as a whole, even in the face of future code
changes or regressions. If you can't force its use, you're not
reducing that risk very much. (If anything, a "require" option that
doesn't actually require it makes the analysis more complicated, not
less...)

Controlling these in HBA is a bit inconvenient, because you only find
out after authentication if it's allowed or not. So if e.g. direct SSL
connections are disabled for a user,

Hopefully disabling direct SSL piecemeal is not a desired use case?
I'm not sure it makes sense to focus on that. Forcing it to be enabled
shouldn't have the same problem, should it?

Forcing it to be enabled piecemeal based on role or database has similar
problems.

Hm. For some reason I thought it was easier the other direction, but I
can't remember why I thought that. I'll withdraw the comment for now
:)

--Jacob

#11Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Michael Paquier (#8)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, Apr 22, 2024 at 10:42 PM Michael Paquier <michael@paquier.xyz> wrote:

On Mon, Apr 22, 2024 at 10:47:51AM +0300, Heikki Linnakangas wrote:

On 22/04/2024 10:19, Michael Paquier wrote:

As a whole, I can get behind a unique GUC that forces the use of
direct. Or, we could extend the existing "ssl" GUC with a new
"direct" value to accept only direct connections and restrict the
original protocol (and a new "postgres" for the pre-16 protocol,
rejecting direct?), while "on" is able to accept both.

I'd be OK with that, although I still don't really see the point of forcing
this from the server side. We could also add this later.

I'd be OK with doing something only in v18, if need be. Jacob, what
do you think?

I think it would be nice to have an option like that. Whether it's
done now or in 18, I don't have a strong opinion about. But I do think
it'd be helpful to have a consensus on whether or not this is a
security improvement, or a performance enhancement only, before adding
said option. As it's implemented, if the requiredirect option doesn't
actually requiredirect, I think it looks like security but isn't
really.

(My ideal server-side option removes all plaintext negotiation and
forces the use of direct SSL for every connection, paired with a new
postgresqls:// scheme for the client. But I don't have any experience
making a switchover like that at scale, and I'd like to avoid a
StartTLS-vs-LDAPS sort of situation. That's obviously not a
conversation for 17.)

As for HBA control: overall, I don't see a burning need for an
HBA-based configuration, honestly. I'd prefer to reduce the number of
knobs and make it easier to apply the strongest security with a broad
brush.

--Jacob

#12Robert Haas
robertmhaas@gmail.com
In reply to: Jacob Champion (#11)
Re: Direct SSL connection with ALPN and HBA rules

On Tue, Apr 23, 2024 at 1:22 PM Jacob Champion
<jacob.champion@enterprisedb.com> wrote:

On Mon, Apr 22, 2024 at 10:42 PM Michael Paquier <michael@paquier.xyz> wrote:

On Mon, Apr 22, 2024 at 10:47:51AM +0300, Heikki Linnakangas wrote:

On 22/04/2024 10:19, Michael Paquier wrote:

As a whole, I can get behind a unique GUC that forces the use of
direct. Or, we could extend the existing "ssl" GUC with a new
"direct" value to accept only direct connections and restrict the
original protocol (and a new "postgres" for the pre-16 protocol,
rejecting direct?), while "on" is able to accept both.

I'd be OK with that, although I still don't really see the point of forcing
this from the server side. We could also add this later.

I'd be OK with doing something only in v18, if need be. Jacob, what
do you think?

I think it would be nice to have an option like that. Whether it's
done now or in 18, I don't have a strong opinion about. But I do think
it'd be helpful to have a consensus on whether or not this is a
security improvement, or a performance enhancement only, before adding
said option. As it's implemented, if the requiredirect option doesn't
actually requiredirect, I think it looks like security but isn't
really.

I've not followed this thread closely enough to understand the comment
about requiredirect maybe not actually requiring direct, but if that
were true it seems like it might be concerning.

But as far as having a GUC to force direct SSL or not, I agree that's
a good idea, and that it's better than only being able to control the
behavior through pg_hba.conf, because it removes room for any possible
doubt about whether you're really enforcing the behavior you want to
be enforcing. It might also mean that the connection can be rejected
earlier in the handshaking process on the basis of the GUC value,
which could conceivably prevent a client from reaching some piece of
code that turns out to have a security vulnerability. For example, if
we found out that direct SSL connections let you take over the
Pentagon before reaching the authentication stage, but for some reason
regular connections don't have the same problem, being able to
categorically shut off direct SSL would be valuable.

However, I don't really see why this has to be done for this release.
It seems like a separate feature from direct SSL itself. If direct SSL
hadn't been committed at the very last minute, then it would have been
great if this had been done for this release, too. But it was. The
moral we ought to take from that is "perhaps get the big features in a
bit further in advance of the freeze," not "well we'll just keep
hacking after the freeze."

--
Robert Haas
EDB: http://www.enterprisedb.com

#13Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Robert Haas (#12)
Re: Direct SSL connection with ALPN and HBA rules

On Tue, Apr 23, 2024 at 10:43 AM Robert Haas <robertmhaas@gmail.com> wrote:

I've not followed this thread closely enough to understand the comment
about requiredirect maybe not actually requiring direct, but if that
were true it seems like it might be concerning.

It may be my misunderstanding. This seems to imply bad behavior:

If the server rejects GSS encryption, SSL is
negotiated over the same TCP connection using the traditional postgres
protocol, regardless of <literal>sslnegotiation</literal>.

As does this comment:

+   /*
+    * If enabled, try direct SSL. Unless we have a valid TCP connection that
+    * failed negotiating GSSAPI encryption or a plaintext connection in case
+    * of sslmode='allow'; in that case we prefer to reuse the connection with
+    * negotiated SSL, instead of reconnecting to do direct SSL. The point of
+    * direct SSL is to avoid the roundtrip from the negotiation, but
+    * reconnecting would also incur a roundtrip.
+    */

but when I actually try those cases, I see that requiredirect does
actually cause a direct SSL connection to be done, even with
sslmode=allow. So maybe it's just misleading documentation (or my
misreading of it) that needs to be expanded? Am I missing a different
corner case where requiredirect is ignored, Heikki?

I still question the utility of allowing sslmode=allow with
sslnegotiation=requiredirect, because it seems like you've made both
the performance and security characteristics actively worse if you
choose that combination. But I want to make sure I understand the
current behavior correctly before I derail the discussion too much...

Thanks,
--Jacob

#14Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Jacob Champion (#13)
1 attachment(s)
Re: Direct SSL connection with ALPN and HBA rules

On 23/04/2024 22:33, Jacob Champion wrote:

On Tue, Apr 23, 2024 at 10:43 AM Robert Haas <robertmhaas@gmail.com> wrote:

I've not followed this thread closely enough to understand the comment
about requiredirect maybe not actually requiring direct, but if that
were true it seems like it might be concerning.

It may be my misunderstanding. This seems to imply bad behavior:

If the server rejects GSS encryption, SSL is
negotiated over the same TCP connection using the traditional postgres
protocol, regardless of <literal>sslnegotiation</literal>.

As does this comment:

+   /*
+    * If enabled, try direct SSL. Unless we have a valid TCP connection that
+    * failed negotiating GSSAPI encryption or a plaintext connection in case
+    * of sslmode='allow'; in that case we prefer to reuse the connection with
+    * negotiated SSL, instead of reconnecting to do direct SSL. The point of
+    * direct SSL is to avoid the roundtrip from the negotiation, but
+    * reconnecting would also incur a roundtrip.
+    */

but when I actually try those cases, I see that requiredirect does
actually cause a direct SSL connection to be done, even with
sslmode=allow. So maybe it's just misleading documentation (or my
misreading of it) that needs to be expanded? Am I missing a different
corner case where requiredirect is ignored, Heikki?

You're right, the comment is wrong about sslmode=allow. There is no
negotiation of a plaintext connection, the client just sends the startup
packet directly. The HBA rules can reject it, but the client will have
to disconnect and reconnect in that case.

The documentation and that comment are misleading about failed GSSAPI
encryption too, and I also misremembered that. With
sslnegotiation=requiredirect, libpq never uses negotiated SSL mode. It
will reconnect after a rejected GSSAPI request. So that comment applies
to sslnegotiation=direct, but not sslnegotiation=requiredirect.

Attached patch tries to fix and clarify those.

(Note that the client will only attempt GSSAPI encryption if it can find
kerberos credentials in the environment.)

--
Heikki Linnakangas
Neon (https://neon.tech)

Attachments:

0001-Fix-documentation-and-comments-on-what-happens-after.patchtext/x-patch; charset=UTF-8; name=0001-Fix-documentation-and-comments-on-what-happens-after.patchDownload
From 664555decb695123a4bf25ea56f789202b926ea0 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 24 Apr 2024 00:10:24 +0300
Subject: [PATCH 1/1] Fix documentation and comments on what happens after GSS
 rejection

The paragraph in the docs and the comment applied to
sslnegotiaton=direct, but not sslnegotiation=requiredirect. In
'requiredirect' mode, negotiated SSL is never used. Move the paragraph
in the docs under the description of 'direct' mode, and rephrase it.

Also the comment's reference to reusing a plaintext connection was
bogus. Authentication failure in plaintext mode only happens after
sending the startup packet, so the connection cannot be reused.
---
 doc/src/sgml/libpq.sgml           | 19 +++++++++----------
 src/interfaces/libpq/fe-connect.c | 11 ++++++-----
 2 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 9199d0d2e5..7f854edfa2 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1803,6 +1803,15 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
              process adds significant latency if the initial SSL connection
              fails.
            </para>
+           <para>
+             An exception is if <literal>gssencmode</literal> is set
+             to <literal>prefer</literal>, but the server rejects GSS encryption.
+             In that case, SSL is negotiated over the same TCP connection using
+             <productname>PostgreSQL</productname> protocol negotiation. In
+             other words, the direct SSL handshake is not used, if a TCP
+             connection has already been established and can be used for the
+             SSL handshake.
+           </para>
           </listitem>
          </varlistentry>
 
@@ -1816,16 +1825,6 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
           </listitem>
          </varlistentry>
         </variablelist>
-
-       <para>
-        Note that if <literal>gssencmode</literal> is set
-        to <literal>prefer</literal>, a <acronym>GSS</acronym> connection is
-        attempted first. If the server rejects GSS encryption, SSL is
-        negotiated over the same TCP connection using the traditional postgres
-        protocol, regardless of <literal>sslnegotiation</literal>. In other
-        words, the direct SSL handshake is not used, if a TCP connection has
-        already been established and can be used for the SSL handshake.
-       </para>
       </listitem>
      </varlistentry>
 
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index ec20e3f3a9..b1d3bfa59d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -4430,11 +4430,12 @@ select_next_encryption_method(PGconn *conn, bool have_valid_connection)
 
 	/*
 	 * If enabled, try direct SSL. Unless we have a valid TCP connection that
-	 * failed negotiating GSSAPI encryption or a plaintext connection in case
-	 * of sslmode='allow'; in that case we prefer to reuse the connection with
-	 * negotiated SSL, instead of reconnecting to do direct SSL. The point of
-	 * direct SSL is to avoid the roundtrip from the negotiation, but
-	 * reconnecting would also incur a roundtrip.
+	 * failed negotiating GSSAPI encryption; in that case we prefer to reuse
+	 * the connection with negotiated SSL, instead of reconnecting to do
+	 * direct SSL. The point of sslnegotiation=direct is to avoid the
+	 * roundtrip from the negotiation, but reconnecting would also incur a
+	 * roundtrip. (In sslnegotiation=requiredirect mode, negotiatied SSL is
+	 * not in the list of allowed methods and we will reconnect.)
 	 */
 	if (have_valid_connection)
 		SELECT_NEXT_METHOD(ENC_NEGOTIATED_SSL);
-- 
2.39.2

#15Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Heikki Linnakangas (#14)
Re: Direct SSL connection with ALPN and HBA rules

On Tue, Apr 23, 2024 at 2:20 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Attached patch tries to fix and clarify those.

s/negotiatied/negotiated/ in the attached patch, but other than that
this seems like a definite improvement. Thanks!

(Note that the client will only attempt GSSAPI encryption if it can find
kerberos credentials in the environment.)

Right. I don't like that it still happens with
sslnegotiation=requiredirect, but I suspect that this is not the
thread to complain about it in. Maybe I can propose a
sslnegotiation=forcedirect or something for 18, to complement a
postgresqls:// scheme.

That leaves the ALPACA handshake correction, I think. (Peter had some
questions on the original thread [1]/messages/by-id/e782e9f4-a0cd-49f5-800b-5e32a1b29183@eisentraut.org that I've tried to answer.) And
the overall consensus, or lack thereof, on whether or not
`requiredirect` should be considered a security feature.

Thanks,
--Jacob

[1]: /messages/by-id/e782e9f4-a0cd-49f5-800b-5e32a1b29183@eisentraut.org

#16Robert Haas
robertmhaas@gmail.com
In reply to: Jacob Champion (#15)
Re: Direct SSL connection with ALPN and HBA rules

On Thu, Apr 25, 2024 at 12:16 PM Jacob Champion
<jacob.champion@enterprisedb.com> wrote

Right. I don't like that it still happens with
sslnegotiation=requiredirect, but I suspect that this is not the
thread to complain about it in. Maybe I can propose a
sslnegotiation=forcedirect or something for 18, to complement a
postgresqls:// scheme.

It is difficult to imagine a world in which we have both requiredirect
and forcedirect and people are not confused.

--
Robert Haas
EDB: http://www.enterprisedb.com

#17Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Robert Haas (#16)
Re: Direct SSL connection with ALPN and HBA rules

On Thu, Apr 25, 2024 at 9:17 AM Robert Haas <robertmhaas@gmail.com> wrote:

It is difficult to imagine a world in which we have both requiredirect
and forcedirect and people are not confused.

Yeah... Any thoughts on a better scheme? require_auth was meant to
lock down overly general authentication; maybe a require_proto or
something could do the same for the transport?

I hate that we have so many options that most people don't need but
take precedence, especially when they're based on the existence of
magic third-party environmental cues (e.g. Kerberos caches). And it
was nice that we got sslrootcert=system to turn on strong security and
reject nonsensical combinations. If someone sets `requiredirect` and
leaves the default sslmode, or chooses a weaker one... Is that really
useful to someone?

--Jacob

#18Robert Haas
robertmhaas@gmail.com
In reply to: Jacob Champion (#17)
Re: Direct SSL connection with ALPN and HBA rules

On Thu, Apr 25, 2024 at 12:28 PM Jacob Champion
<jacob.champion@enterprisedb.com> wrote:

On Thu, Apr 25, 2024 at 9:17 AM Robert Haas <robertmhaas@gmail.com> wrote:

It is difficult to imagine a world in which we have both requiredirect
and forcedirect and people are not confused.

Yeah... Any thoughts on a better scheme? require_auth was meant to
lock down overly general authentication; maybe a require_proto or
something could do the same for the transport?

I don't understand the difference between the two sets of semantics
myself, so I'm not in a good position to comment.

I hate that we have so many options that most people don't need but
take precedence, especially when they're based on the existence of
magic third-party environmental cues (e.g. Kerberos caches). And it
was nice that we got sslrootcert=system to turn on strong security and
reject nonsensical combinations. If someone sets `requiredirect` and
leaves the default sslmode, or chooses a weaker one... Is that really
useful to someone?

Maybe I'm missing something here, but why doesn't sslnegotiation
override sslmode completely? Or alternatively, why not remove
sslnegotiation entirely and just have more sslmode values? I mean
maybe this shouldn't happen categorically, but if I say I want to
require a direct SSL connection, to me that implies that I don't want
an indirect SSL connection, and I really don't want a non-SSL
connection.

I think it's pretty questionable in 2024 whether sslmode=allow and
sslmode=prefer make any sense at all. I don't think it would be crazy
to remove them entirely. But I certainly don't think that they should
be allowed to bleed into the behavior of new, higher-security
configurations. Surely if I say I want direct SSL, it's that or
nothing, right?

--
Robert Haas
EDB: http://www.enterprisedb.com

#19Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Robert Haas (#18)
Re: Direct SSL connection with ALPN and HBA rules

On Thu, Apr 25, 2024 at 10:35 AM Robert Haas <robertmhaas@gmail.com> wrote:

Maybe I'm missing something here, but why doesn't sslnegotiation
override sslmode completely? Or alternatively, why not remove
sslnegotiation entirely and just have more sslmode values? I mean
maybe this shouldn't happen categorically, but if I say I want to
require a direct SSL connection, to me that implies that I don't want
an indirect SSL connection, and I really don't want a non-SSL
connection.

I think that comes down to the debate upthread, and whether you think
it's a performance tweak or a security feature. My take on it is,
`direct` mode is performance, and `requiredirect` is security.
(Especially since, with the current implementation, requiredirect can
slow things down?)

I think it's pretty questionable in 2024 whether sslmode=allow and
sslmode=prefer make any sense at all. I don't think it would be crazy
to remove them entirely. But I certainly don't think that they should
be allowed to bleed into the behavior of new, higher-security
configurations. Surely if I say I want direct SSL, it's that or
nothing, right?

I agree, but I more or less lost the battle at [1]/messages/by-id/CAOYmi+=cnV-8V8TndSkEF6Htqa7qHQUL_KnQU8-DrT0Jjnm3_Q@mail.gmail.com. Like Matthias
mentioned in [2]/messages/by-id/CAEze2Wi9j5Q3mRnuoD2Hr=eOFV-cMzWAUZ88YmSXSwsiJLQOWA@mail.gmail.com:

I'm not sure about this either. The 'gssencmode' option is already
quite weird in that it seems to override the "require"d priority of
"sslmode=require", which it IMO really shouldn't.

Thanks,
--Jacob

[1]: /messages/by-id/CAOYmi+=cnV-8V8TndSkEF6Htqa7qHQUL_KnQU8-DrT0Jjnm3_Q@mail.gmail.com
[2]: /messages/by-id/CAEze2Wi9j5Q3mRnuoD2Hr=eOFV-cMzWAUZ88YmSXSwsiJLQOWA@mail.gmail.com

#20Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Jacob Champion (#19)
Re: Direct SSL connection with ALPN and HBA rules

On 25/04/2024 21:13, Jacob Champion wrote:

On Thu, Apr 25, 2024 at 10:35 AM Robert Haas <robertmhaas@gmail.com> wrote:

Maybe I'm missing something here, but why doesn't sslnegotiation
override sslmode completely? Or alternatively, why not remove
sslnegotiation entirely and just have more sslmode values? I mean
maybe this shouldn't happen categorically, but if I say I want to
require a direct SSL connection, to me that implies that I don't want
an indirect SSL connection, and I really don't want a non-SSL
connection.

My thinking with sslnegotiation is that it controls how SSL is
negotiated with the server, if SSL is to be used at all. It does not
control whether SSL is used or required; that's what sslmode is for.

I think that comes down to the debate upthread, and whether you think
it's a performance tweak or a security feature. My take on it is,
`direct` mode is performance, and `requiredirect` is security.

Agreed, although the the security benefits from `requiredirect` are
pretty vague. It reduces the attack surface, but there are no known
issues with the 'postgres' or 'direct' negotiation either.

Perhaps 'requiredirect' should be renamed to 'directonly'?

(Especially since, with the current implementation, requiredirect can
slow things down?)

Yes: the case is gssencmode=prefer, kerberos credentical cache present
in client, and server doesn't support GSS. With
sslnegotiation='postgres' or 'direct', libpq can do the SSL negotiation
over the same TCP connection after the server rejected the GSSRequest.
With sslnegotiation='requiredirect', it needs to open a new TCP connection.

I think it's pretty questionable in 2024 whether sslmode=allow and

sslmode=prefer make any sense at all. I don't think it would be crazy
to remove them entirely. But I certainly don't think that they should
be allowed to bleed into the behavior of new, higher-security
configurations. Surely if I say I want direct SSL, it's that or
nothing, right?

I agree, but I more or less lost the battle at [1]. Like Matthias
mentioned in [2]:

I'm not sure about this either. The 'gssencmode' option is already
quite weird in that it seems to override the "require"d priority of
"sslmode=require", which it IMO really shouldn't.

Yeah, that combination is weird. I think we should forbid it. But that's
separate from sslnegotiation.

--
Heikki Linnakangas
Neon (https://neon.tech)

#21Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Heikki Linnakangas (#20)
Re: Direct SSL connection with ALPN and HBA rules

On Thu, Apr 25, 2024 at 2:50 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

I think that comes down to the debate upthread, and whether you think
it's a performance tweak or a security feature. My take on it is,
`direct` mode is performance, and `requiredirect` is security.

Agreed, although the the security benefits from `requiredirect` are
pretty vague. It reduces the attack surface, but there are no known
issues with the 'postgres' or 'direct' negotiation either.

I think reduction in attack surface is a concrete security benefit,
not a vague one. True, I don't know of any exploits today, but that
seems almost tautological -- if there were known exploits in our
upgrade handshake, I assume we'd be working to fix them ASAP?

Perhaps 'requiredirect' should be renamed to 'directonly'?

If it's agreed that we don't want to require a stronger sslmode for
that sslnegotiation setting, then that would probably be an
improvement. But who is the target user for
`sslnegotiation=directonly`, in your opinion? Would they ever have a
reason to use a weak sslmode?

I'm not sure about this either. The 'gssencmode' option is already
quite weird in that it seems to override the "require"d priority of
"sslmode=require", which it IMO really shouldn't.

Yeah, that combination is weird. I think we should forbid it. But that's
separate from sslnegotiation.

Separate but related, IMO. If we were all hypothetically okay with
gssencmode ignoring `sslmode=require`, then it's hard for me to claim
that `sslnegotiation=requiredirect` should behave differently. On the
other hand, if we're not okay with that and we'd like to change it,
it's easier for me to argue that `requiredirect` should also be
stricter from the get-go.

Thanks,
--Jacob

#22Robert Haas
robertmhaas@gmail.com
In reply to: Heikki Linnakangas (#20)
Re: Direct SSL connection with ALPN and HBA rules

On Thu, Apr 25, 2024 at 5:50 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 25/04/2024 21:13, Jacob Champion wrote:

On Thu, Apr 25, 2024 at 10:35 AM Robert Haas <robertmhaas@gmail.com> wrote:

Maybe I'm missing something here, but why doesn't sslnegotiation
override sslmode completely? Or alternatively, why not remove
sslnegotiation entirely and just have more sslmode values? I mean
maybe this shouldn't happen categorically, but if I say I want to
require a direct SSL connection, to me that implies that I don't want
an indirect SSL connection, and I really don't want a non-SSL
connection.

My thinking with sslnegotiation is that it controls how SSL is
negotiated with the server, if SSL is to be used at all. It does not
control whether SSL is used or required; that's what sslmode is for.

I think this might boil down to the order in which someone thinks that
different settings should be applied. It sounds like your mental model
is that GSS settings are applied first, and then SSL settings are
applied afterwards, and then within the SSL bucket you can select how
you want to do SSL (direct or negotiated) and how required it is. My
mental model is different: I imagine that since direct SSL happens
from the first byte exchanged over the socket, direct SSL "happens
first", making settings that pertain to negotiated GSS and negotiated
SSL irrelevant. Because, logically, if you've decided to use direct
SSL, you're not even going to get a chance to negotiate those things.
I understand that the code as written works around that, by being able
to open a new connection if it turns out that we need to negotiate
that stuff after all, but IMHO that's rather confusing.

--
Robert Haas
EDB: http://www.enterprisedb.com

#23Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Jacob Champion (#10)
1 attachment(s)
Re: Direct SSL connection with ALPN and HBA rules

On 23/04/2024 20:02, Jacob Champion wrote:

On Fri, Apr 19, 2024 at 2:43 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 19/04/2024 19:48, Jacob Champion wrote:

On Fri, Apr 19, 2024 at 6:56 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

With direct SSL negotiation, we always require ALPN.

(As an aside: I haven't gotten to test the version of the patch that
made it into 17 yet, but from a quick glance it looks like we're not
rejecting mismatched ALPN during the handshake as noted in [1].)

Ah, good catch, that fell through the cracks. Agreed, the client should
reject a direct SSL connection if the server didn't send ALPN. I'll add
that to the Open Items so we don't forget again.

Yes, the client should also reject, but that's not what I'm referring
to above. The server needs to fail the TLS handshake itself with the
proper error code (I think it's `no_application_protocol`?); otherwise
a client implementing a different protocol could consume the
application-level bytes coming back from the server and act on them.
That's the protocol confusion attack from ALPACA we're trying to
avoid.

I finally understood what you mean. So if the client supports ALPN, but
the list of protocols that it provides does not include 'postgresql',
the server should reject the connection with 'no_applicaton_protocol'
alert. Makes sense. I thought OpenSSL would do that with the alpn
callback we have, but it does not.

The attached patch makes that change. I used the alpn_cb() function in
openssl's own s_server program as example for that.

Unfortunately the error message you got in the client with that was
horrible (I modified the server to not accept the 'postgresql' protocol):

psql "dbname=postgres sslmode=require host=localhost"
psql: error: connection to server at "localhost" (::1), port 5432
failed: SSL error: SSL error code 167773280

This is similar to the case with system errors discussed at
/messages/by-id/b6fb018b-f05c-4afd-abd3-318c649faf18@highgo.ca, but
this one is equally bad on OpenSSL 1.1.1 and 3.3.0. It seems like an
OpenSSL bug to me, because there is an error string "no application
protocol" in the OpenSSL sources (ssl/ssl_err.c):

{ERR_PACK(ERR_LIB_SSL, 0, SSL_R_NO_APPLICATION_PROTOCOL),
"no application protocol"},

and in the server log, you get that message. But the error code seen in
the client is different. There are also messages for other alerts, for
example:

{ERR_PACK(ERR_LIB_SSL, 0, SSL_R_TLSV13_ALERT_MISSING_EXTENSION),
"tlsv13 alert missing extension"},

The bottom line is that that seems like a bug of omission to me in
OpenSSL, but I wouldn't hold my breath waiting for it to be fixed. We
can easily check for that error code and print the right message
ourselves however, as in the attached patch.

--
Heikki Linnakangas
Neon (https://neon.tech)

Attachments:

0001-Reject-SSL-connection-if-ALPN-is-used-but-there-s-no.patchtext/x-patch; charset=UTF-8; name=0001-Reject-SSL-connection-if-ALPN-is-used-but-there-s-no.patchDownload
From 125e9adda6cdab644b660772e29c713863e93cc2 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Sat, 27 Apr 2024 01:47:55 +0300
Subject: [PATCH 1/1] Reject SSL connection if ALPN is used but there's no
 common protocol

If the client supports ALPN but tries to use some other protocol, like
HTTPS, reject the connection in the server. That is surely a confusion
of some sort like trying to PostgreSQL server with a
browser. Furthermore, the ALPN RFC 7301 says:

> In the event that the server supports no protocols that the client
> advertises, then the server SHALL respond with a fatal
> "no_application_protocol" alert.

This commit makes the server follow that advice.

In the client, specifically check for the OpenSSL error code for the
"no_application_protocol" alert. Otherwise you got a cryptic "SSL
error: SSL error code 167773280" error if you tried to connect to a
non-PostgreSQL server that rejects the connection with
"no_application_protocol". ERR_reason_error_string() returns NULL for
that code, which frankly seems like an OpenSSL bug to me, but we can
easily print a better message ourselves.
---
 src/backend/libpq/be-secure-openssl.c    | 10 +++++++---
 src/interfaces/libpq/fe-secure-openssl.c | 12 ++++++++++++
 2 files changed, 19 insertions(+), 3 deletions(-)

diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index fc46a33539..60cf68aac4 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1336,10 +1336,14 @@ alpn_cb(SSL *ssl,
 
 	if (retval == OPENSSL_NPN_NEGOTIATED)
 		return SSL_TLSEXT_ERR_OK;
-	else if (retval == OPENSSL_NPN_NO_OVERLAP)
-		return SSL_TLSEXT_ERR_NOACK;
 	else
-		return SSL_TLSEXT_ERR_NOACK;
+	{
+		/*
+		 * The client doesn't support our protocol.  Reject the connection
+		 * with TLS "no_application_protocol" alert, per RFC 7301.
+		 */
+		return SSL_TLSEXT_ERR_ALERT_FATAL;
+	}
 }
 
 
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 0de21dc7e4..ee1a47f2b1 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -1741,6 +1741,18 @@ SSLerrmessage(unsigned long ecode)
 		return errbuf;
 	}
 
+	if (ERR_GET_LIB(ecode) == ERR_LIB_SSL &&
+		ERR_GET_REASON(ecode) == SSL_AD_REASON_OFFSET + SSL_AD_NO_APPLICATION_PROTOCOL)
+	{
+		/*
+		 * Server aborted the connection with TLS "no_application_protocol"
+		 * alert.  The ERR_reason_error_string() function doesn't give any
+		 * error string for that for some reason, so do it ourselves.
+		 */
+		snprintf(errbuf, SSL_ERR_LEN, libpq_gettext("no application protocol"));
+		return errbuf;
+	}
+
 	/*
 	 * In OpenSSL 3.0.0 and later, ERR_reason_error_string randomly refuses to
 	 * map system errno values.  We can cover that shortcoming with this bit
-- 
2.39.2

#24Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Jacob Champion (#21)
Re: Direct SSL connection with ALPN and HBA rules

On 26/04/2024 02:23, Jacob Champion wrote:

On Thu, Apr 25, 2024 at 2:50 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

I think that comes down to the debate upthread, and whether you think
it's a performance tweak or a security feature. My take on it is,
`direct` mode is performance, and `requiredirect` is security.

Agreed, although the the security benefits from `requiredirect` are
pretty vague. It reduces the attack surface, but there are no known
issues with the 'postgres' or 'direct' negotiation either.

I think reduction in attack surface is a concrete security benefit,
not a vague one. True, I don't know of any exploits today, but that
seems almost tautological -- if there were known exploits in our
upgrade handshake, I assume we'd be working to fix them ASAP?

Sure, we'd try to fix them ASAP. But we've had the SSLRequest
negotiation since time immemorial. If a new vulnerability is found, it's
unlikely that we'd need to disable the SSLRequest negotiation altogether
to fix it. We'd be in serious trouble with back-branches in that case.
There's no sudden need to have a kill-switch for it.

Taking that to the extreme, you could argue for a kill-switch for every
feature, just in case there's a vulnerability in them. I agree that
authentication is more sensitive so reducing the surface of that is more
reasonable. And but nevertheless.

(This discussion is moot though, because we do have the
sslnegotiation=requiredirect mode, so you can disable the SSLRequest
negotiation.)

Perhaps 'requiredirect' should be renamed to 'directonly'?

If it's agreed that we don't want to require a stronger sslmode for
that sslnegotiation setting, then that would probably be an
improvement. But who is the target user for
`sslnegotiation=directonly`, in your opinion? Would they ever have a
reason to use a weak sslmode?

It's unlikely, I agree. A user who's sophisticated enough to use
sslnegotiation=directonly would probably also want sslmode=require and
require_auth=scram-sha256 and channel_binding=require. Or
sslmode=verify-full. But in principle they're orthogonal. If you only
have v17 servers in your environment, so you know all servers support
direct negotiation if they support SSL at all, but a mix of servers with
and without SSL, sslnegotiation=directonly would reduce roundtrips with
sslmode=prefer.

Making requiredirect to imply sslmode=require, or error out unless you
also set sslmode=require, feels like a cavalier way of forcing SSL. We
should have a serious discussion on making sslmode=require the default
instead. That would be a more direct way of nudging people to use SSL.
It would cause a lot of breakage, but it would also be a big improvement
to security.

Consider how sslnegotiation=requiredirect/directonly would feel, if we
made sslmode=require the default. If you explicitly set "sslmode=prefer"
or "sslmode=disable", it would be annoying if you would also need to
remove "sslnegotiation=requiredirect" from your connection string.

I'm leaning towards renaming sslnegotiation=requiredirect to
sslnegotiation=directonly at this point.

I'm not sure about this either. The 'gssencmode' option is already
quite weird in that it seems to override the "require"d priority of
"sslmode=require", which it IMO really shouldn't.

Yeah, that combination is weird. I think we should forbid it. But that's
separate from sslnegotiation.

Separate but related, IMO. If we were all hypothetically okay with
gssencmode ignoring `sslmode=require`, then it's hard for me to claim
that `sslnegotiation=requiredirect` should behave differently. On the
other hand, if we're not okay with that and we'd like to change it,
it's easier for me to argue that `requiredirect` should also be
stricter from the get-go.

I think the best way forward for those is something like a new
"require_proto" parameter that you suggested upthread. Perhaps call it
"encryption", with options "none", "ssl", "gss" so that you can provide
multiple options and libpq will try them in the order specified. For
example:

encryption=none
encryption=ssl, none # like sslmode=prefer
encryption=gss
encryption=gss, ssl # try GSS first, then SSL
encryption=ssl, gss # try SSL first, then GSS

This would make gssencmode and sslmode=disable/allow/prefer/require
settings obsolete. sslmode would stil be needed to distinguish between
verify-ca/verify-full though. But sslnegotiation would be still
orthogonal to that.

--
Heikki Linnakangas
Neon (https://neon.tech)

#25Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Michael Paquier (#9)
Re: Direct SSL connection with ALPN and HBA rules

On 23/04/2024 10:07, Michael Paquier wrote:

In the documentation of PQsslAttribute(), it is mentioned that empty
string is returned for "alpn" if ALPN was not used, however the code
returns NULL in this case:
SSL_get0_alpn_selected(conn->ssl, &data, &len);
if (data == NULL || len == 0 || len > sizeof(alpn_str) - 1)
return NULL;

Good catch. I changed the code to return an empty string, as the
documentation says.

I considered if NULL or empty string would be better here. The docs for
PQsslAttribute also says:

"Returns NULL if the connection does not use SSL or the specified
attribute name is not defined for the library in use."

If a caller wants to distinguish between "libpq or the SSL library
doesn't support ALPN at all" from "the server didn't support ALPN", you
can tell from whether PQsslAttribute returns NULL or an empty string. So
I think an empty string is better.

--
Heikki Linnakangas
Neon (https://neon.tech)

#26Michael Paquier
michael@paquier.xyz
In reply to: Heikki Linnakangas (#25)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, Apr 29, 2024 at 12:43:18PM +0300, Heikki Linnakangas wrote:

If a caller wants to distinguish between "libpq or the SSL library doesn't
support ALPN at all" from "the server didn't support ALPN", you can tell
from whether PQsslAttribute returns NULL or an empty string. So I think an
empty string is better.

Thanks. I would also have used an empty string to differenciate these
two cases.
--
Michael

#27Robert Haas
robertmhaas@gmail.com
In reply to: Heikki Linnakangas (#24)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, Apr 29, 2024 at 4:38 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Making requiredirect to imply sslmode=require, or error out unless you
also set sslmode=require, feels like a cavalier way of forcing SSL. We
should have a serious discussion on making sslmode=require the default
instead. That would be a more direct way of nudging people to use SSL.
It would cause a lot of breakage, but it would also be a big improvement
to security.

Consider how sslnegotiation=requiredirect/directonly would feel, if we
made sslmode=require the default. If you explicitly set "sslmode=prefer"
or "sslmode=disable", it would be annoying if you would also need to
remove "sslnegotiation=requiredirect" from your connection string.

I think making sslmode=require the default is pretty unworkable,
unless we also had a way of automatically setting up SSL as part of
initdb or something. Otherwise, we'd have to add sslmode=disable to a
million places just to get the regression tests to work, and every
test cluster anyone spins up locally would break in annoying ways,
too. I had been thinking we might want to change the default to
sslmode=disable and remove allow and prefer, but maybe automating a
basic SSL setup is better. Either way, we should move toward a world
where you either ask for SSL and get it, or don't ask for it and don't
get it. Being halfway in between is bad.

--
Robert Haas
EDB: http://www.enterprisedb.com

#28Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Heikki Linnakangas (#23)
Re: Direct SSL connection with ALPN and HBA rules

On Fri, Apr 26, 2024 at 3:51 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

I finally understood what you mean. So if the client supports ALPN, but
the list of protocols that it provides does not include 'postgresql',
the server should reject the connection with 'no_applicaton_protocol'
alert.

Right. (And additionally, we reject clients that don't advertise ALPN
over direct SSL, also during the TLS handshake.)

The attached patch makes that change. I used the alpn_cb() function in
openssl's own s_server program as example for that.

This patch as written will apply the new requirement to the old
negotiation style, though, won't it? My test suite sees a bunch of
failures with that.

Unfortunately the error message you got in the client with that was
horrible (I modified the server to not accept the 'postgresql' protocol):

psql "dbname=postgres sslmode=require host=localhost"
psql: error: connection to server at "localhost" (::1), port 5432
failed: SSL error: SSL error code 167773280

<long sigh>

I filed a bug upstream [1]https://github.com/openssl/openssl/issues/24300.

Thanks,
--Jacob

[1]: https://github.com/openssl/openssl/issues/24300

#29Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Heikki Linnakangas (#24)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, Apr 29, 2024 at 1:38 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Sure, we'd try to fix them ASAP. But we've had the SSLRequest
negotiation since time immemorial. If a new vulnerability is found, it's
unlikely that we'd need to disable the SSLRequest negotiation altogether
to fix it. We'd be in serious trouble with back-branches in that case.
There's no sudden need to have a kill-switch for it.

I'm not really arguing that you'd need the kill switch to fix a
problem in the code. (At least, I'm not arguing that in this thread; I
reserve the right to argue that in the future. :D) But between the
point of time that a vulnerability is announced and a user has
upgraded, it's really nice to have a switch as a mitigation. Even
better if it's server-side, because then the DBA can protect all their
clients without requiring action on their part.

Taking that to the extreme, you could argue for a kill-switch for every
feature, just in case there's a vulnerability in them. I agree that
authentication is more sensitive so reducing the surface of that is more
reasonable. And but nevertheless.

I mean... that would be extreme, yeah. I don't think anyone's proposed that.

If you only
have v17 servers in your environment, so you know all servers support
direct negotiation if they support SSL at all, but a mix of servers with
and without SSL, sslnegotiation=directonly would reduce roundtrips with
sslmode=prefer.

But if you're in that situation, what does the use of directonly give
you over `sslnegotiation=direct`? You already know that servers
support direct, so there's no additional performance penalty from the
less strict mode.

Making requiredirect to imply sslmode=require, or error out unless you
also set sslmode=require, feels like a cavalier way of forcing SSL. We
should have a serious discussion on making sslmode=require the default
instead. That would be a more direct way of nudging people to use SSL.
It would cause a lot of breakage, but it would also be a big improvement
to security.

Consider how sslnegotiation=requiredirect/directonly would feel, if we
made sslmode=require the default. If you explicitly set "sslmode=prefer"
or "sslmode=disable", it would be annoying if you would also need to
remove "sslnegotiation=requiredirect" from your connection string.

That's similar to how sslrootcert=system already works. To me, it
feels great, because I don't have to worry about nonsensical
combinations (with the exception of GSS, which we've touched on
above). libpq complains loudly if I try to shoot myself in the foot,
and if I'm using sslrootcert=system then it's a pretty clear signal
that I care more about security than the temporary inconvenience of
editing my connection string for one weird server that doesn't use SSL
for some reason.

I think the best way forward for those is something like a new
"require_proto" parameter that you suggested upthread. Perhaps call it
"encryption", with options "none", "ssl", "gss" so that you can provide
multiple options and libpq will try them in the order specified. For
example:

encryption=none
encryption=ssl, none # like sslmode=prefer
encryption=gss
encryption=gss, ssl # try GSS first, then SSL
encryption=ssl, gss # try SSL first, then GSS

This would make gssencmode and sslmode=disable/allow/prefer/require
settings obsolete. sslmode would stil be needed to distinguish between
verify-ca/verify-full though. But sslnegotiation would be still
orthogonal to that.

I will give this some more thought.

Thanks,
--Jacob

#30Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Jacob Champion (#28)
Re: Direct SSL connection with ALPN and HBA rules

On 29/04/2024 21:04, Jacob Champion wrote:

On Fri, Apr 26, 2024 at 3:51 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

I finally understood what you mean. So if the client supports ALPN, but
the list of protocols that it provides does not include 'postgresql',
the server should reject the connection with 'no_applicaton_protocol'
alert.

Right. (And additionally, we reject clients that don't advertise ALPN
over direct SSL, also during the TLS handshake.)

The attached patch makes that change. I used the alpn_cb() function in
openssl's own s_server program as example for that.

This patch as written will apply the new requirement to the old
negotiation style, though, won't it? My test suite sees a bunch of
failures with that.

Yes, and that is what we want, right? If the client uses old negotiation
style, and includes ALPN in its ClientHello, but requests protocol
"noodles" instead of "postgresql", it seems good to reject the connection.

Note that if the client does not request ALPN at all, the callback is
not called, and the connection is accepted. Old clients still work
because they do not request ALPN.

Unfortunately the error message you got in the client with that was
horrible (I modified the server to not accept the 'postgresql' protocol):

psql "dbname=postgres sslmode=require host=localhost"
psql: error: connection to server at "localhost" (::1), port 5432
failed: SSL error: SSL error code 167773280

<long sigh>

I filed a bug upstream [1].

Thanks!

--
Heikki Linnakangas
Neon (https://neon.tech)

#31Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Heikki Linnakangas (#30)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, Apr 29, 2024 at 11:43 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Note that if the client does not request ALPN at all, the callback is
not called, and the connection is accepted. Old clients still work
because they do not request ALPN.

Ugh, sorry for the noise -- I couldn't figure out why all my old
clients were failing and then realized it was because I'd left some
test code in place for the OpenSSL bug. I'll rebuild everything and
keep reviewing.

--Jacob

#32Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Jacob Champion (#29)
Re: Direct SSL connection with ALPN and HBA rules

On 29/04/2024 21:43, Jacob Champion wrote:

On Mon, Apr 29, 2024 at 1:38 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

If you only
have v17 servers in your environment, so you know all servers support
direct negotiation if they support SSL at all, but a mix of servers with
and without SSL, sslnegotiation=directonly would reduce roundtrips with
sslmode=prefer.

But if you're in that situation, what does the use of directonly give
you over `sslnegotiation=direct`? You already know that servers
support direct, so there's no additional performance penalty from the
less strict mode.

Well, by that argument we don't need requiredirect/directonly at all.
This goes back to whether it's a security feature or a performance feature.

There is a small benefit with sslmode=prefer if you connect to a server
that doesn't support SSL, though. With sslnegotiation=direct, if the
server rejects the direct SSL connection, the client will reconnect and
try SSL with SSLRequest. The server will respond with 'N', and the
client will proceed without encryption. sslnegotiation=directonly
removes that SSLRequest attempt, eliminating one roundtrip.

Making requiredirect to imply sslmode=require, or error out unless you
also set sslmode=require, feels like a cavalier way of forcing SSL. We
should have a serious discussion on making sslmode=require the default
instead. That would be a more direct way of nudging people to use SSL.
It would cause a lot of breakage, but it would also be a big improvement
to security.

Consider how sslnegotiation=requiredirect/directonly would feel, if we
made sslmode=require the default. If you explicitly set "sslmode=prefer"
or "sslmode=disable", it would be annoying if you would also need to
remove "sslnegotiation=requiredirect" from your connection string.

That's similar to how sslrootcert=system already works. To me, it
feels great, because I don't have to worry about nonsensical
combinations (with the exception of GSS, which we've touched on
above). libpq complains loudly if I try to shoot myself in the foot,
and if I'm using sslrootcert=system then it's a pretty clear signal
that I care more about security than the temporary inconvenience of
editing my connection string for one weird server that doesn't use SSL
for some reason.

Oh I was not aware sslrootcert=system works like that. That's a bit
surprising, none of the other ssl-related settings imply or require that
SSL is actually used. Did we intend to set a precedence for new settings
with that?

(adding Daniel in case he has an opinion)

--
Heikki Linnakangas
Neon (https://neon.tech)

#33Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Heikki Linnakangas (#32)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, Apr 29, 2024 at 12:06 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 29/04/2024 21:43, Jacob Champion wrote:

But if you're in that situation, what does the use of directonly give
you over `sslnegotiation=direct`? You already know that servers
support direct, so there's no additional performance penalty from the
less strict mode.

Well, by that argument we don't need requiredirect/directonly at all.
This goes back to whether it's a security feature or a performance feature.

That's what I've been trying to argue, yeah. If it's not a security
feature... why's it there?

There is a small benefit with sslmode=prefer if you connect to a server
that doesn't support SSL, though. With sslnegotiation=direct, if the
server rejects the direct SSL connection, the client will reconnect and
try SSL with SSLRequest. The server will respond with 'N', and the
client will proceed without encryption. sslnegotiation=directonly
removes that SSLRequest attempt, eliminating one roundtrip.

Okay, agreed that in this case, there is a performance benefit. It's
not enough to convince me, honestly, but are there any other cases I
missed as well?

Oh I was not aware sslrootcert=system works like that. That's a bit
surprising, none of the other ssl-related settings imply or require that
SSL is actually used.

For sslrootcert=system in particular, the danger of accidentally weak
sslmodes is pretty high, especially for verify-ca mode. (It goes back
to that other argument -- there should be, effectively, zero users who
both opt in to the public CA system, and are also okay with silently
falling back and not using it.)

Did we intend to set a precedence for new settings
with that?

(I'll let committers answer whether they intended that or not -- I was
just bringing up that we already have a setting that works like that,
and I really like how it works in practice. But it's probably
unsurprising that I like it.)

--Jacob

#34Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Jacob Champion (#33)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, Apr 29, 2024 at 12:32 PM Jacob Champion
<jacob.champion@enterprisedb.com> wrote:

On Mon, Apr 29, 2024 at 12:06 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 29/04/2024 21:43, Jacob Champion wrote:

But if you're in that situation, what does the use of directonly give
you over `sslnegotiation=direct`? You already know that servers
support direct, so there's no additional performance penalty from the
less strict mode.

Well, by that argument we don't need requiredirect/directonly at all.
This goes back to whether it's a security feature or a performance feature.

That's what I've been trying to argue, yeah. If it's not a security
feature... why's it there?

Er, I should clarify this. I _want_ requiredirect. I just want it to
be a security feature.

--Jacob

#35Daniel Gustafsson
daniel@yesql.se
In reply to: Heikki Linnakangas (#32)
Re: Direct SSL connection with ALPN and HBA rules

On 29 Apr 2024, at 21:06, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Oh I was not aware sslrootcert=system works like that. That's a bit surprising, none of the other ssl-related settings imply or require that SSL is actually used. Did we intend to set a precedence for new settings with that?

It was very much intentional, and documented, an sslmode other than verify-full
makes little sense when combined with sslrootcert=system. It wasn't intended
to set a precedence (though there is probably a fair bit of things we can do,
getting this right is hard enough as it is), rather it was footgun prevention.

--
Daniel Gustafsson

#36Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Jacob Champion (#33)
Re: Direct SSL connection with ALPN and HBA rules

On 29/04/2024 22:32, Jacob Champion wrote:

On Mon, Apr 29, 2024 at 12:06 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

There is a small benefit with sslmode=prefer if you connect to a server
that doesn't support SSL, though. With sslnegotiation=direct, if the
server rejects the direct SSL connection, the client will reconnect and
try SSL with SSLRequest. The server will respond with 'N', and the
client will proceed without encryption. sslnegotiation=directonly
removes that SSLRequest attempt, eliminating one roundtrip.

Okay, agreed that in this case, there is a performance benefit. It's
not enough to convince me, honestly, but are there any other cases I
missed as well?

I realized one case that hasn't been discussed so far: If you use the
combination of "sslmode=prefer sslnegotiation=requiredirect" to connect
to a pre-v17 server that has SSL enabled but does not support direct SSL
connections, you will fall back to a plaintext connection instead.
That's almost certainly not what you wanted. I'm coming around to your
opinion that we should not allow that combination.

Stepping back to summarize my thoughts, there are now three things I
don't like about the status quo:

1. As noted above, the sslmode=prefer and sslnegotiation=requiredirect
combination is somewhat dangerous, as you might unintentionally fall
back to plaintext authentication when connecting to a pre-v17 server.

2. There is an asymmetry between "postgres" and "direct"
option names. "postgres" means "try only traditional negotiation", while
"direct" means "try direct first, and fall back to traditional
negotiation if it fails". That is apparent only if you know that the
"requiredirect" mode also exists.

3. The "require" word in "requiredirect" suggests that it's somehow
more strict or more secure, similar to sslmode=require. However, I don't
consider direct SSL connections to be a security feature.

New proposal:

- Remove the "try both" mode completely, and rename "requiredirect" to
just "direct". So there would be just two modes: "postgres" and
"direct". On reflection, the automatic fallback mode doesn't seem very
useful. It would make sense as the default, because then you would get
the benefits automatically in most cases but still be compatible with
old servers. But if it's not the default, you have to fiddle with libpq
settings anyway to enable it, and then you might as well use the
"requiredirect" mode when you know the server supports it. There isn't
anything wrong with it as such, but given how much confusion there's
been on how this all works, I'd prefer to cut this back to the bare
minimum now. We can add it back in the future, and perhaps make it the
default at the same time. This addresses points 2. and 3. above.

and:

- Only allow sslnegotiation=direct with sslmode=require or higher. This
is what you, Jacob, wanted to do all along, and addresses point 1.

Thoughts?

--
Heikki Linnakangas
Neon (https://neon.tech)

#37Jelte Fennema-Nio
postgres@jeltef.nl
In reply to: Heikki Linnakangas (#36)
Re: Direct SSL connection with ALPN and HBA rules

On Fri, 10 May 2024 at 15:50, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

New proposal:

- Remove the "try both" mode completely, and rename "requiredirect" to
just "direct". So there would be just two modes: "postgres" and
"direct". On reflection, the automatic fallback mode doesn't seem very
useful. It would make sense as the default, because then you would get
the benefits automatically in most cases but still be compatible with
old servers. But if it's not the default, you have to fiddle with libpq
settings anyway to enable it, and then you might as well use the
"requiredirect" mode when you know the server supports it. There isn't
anything wrong with it as such, but given how much confusion there's
been on how this all works, I'd prefer to cut this back to the bare
minimum now. We can add it back in the future, and perhaps make it the
default at the same time. This addresses points 2. and 3. above.

and:

- Only allow sslnegotiation=direct with sslmode=require or higher. This
is what you, Jacob, wanted to do all along, and addresses point 1.

Thoughts?

Sounds mostly good to me. But I think we'd want to automatically
increase sslmode to require if it is unset, but sslnegotation is set
to direct. Similar to how we bump sslmode to verify-full if
sslrootcert is set to system, but sslmode is unset. i.e. it seems
unnecessary/unwanted to throw an error if the connection string only
contains sslnegotiation=direct

#38Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Jelte Fennema-Nio (#37)
Re: Direct SSL connection with ALPN and HBA rules

On 11/05/2024 23:45, Jelte Fennema-Nio wrote:

On Fri, 10 May 2024 at 15:50, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

New proposal:

- Remove the "try both" mode completely, and rename "requiredirect" to
just "direct". So there would be just two modes: "postgres" and
"direct". On reflection, the automatic fallback mode doesn't seem very
useful. It would make sense as the default, because then you would get
the benefits automatically in most cases but still be compatible with
old servers. But if it's not the default, you have to fiddle with libpq
settings anyway to enable it, and then you might as well use the
"requiredirect" mode when you know the server supports it. There isn't
anything wrong with it as such, but given how much confusion there's
been on how this all works, I'd prefer to cut this back to the bare
minimum now. We can add it back in the future, and perhaps make it the
default at the same time. This addresses points 2. and 3. above.

and:

- Only allow sslnegotiation=direct with sslmode=require or higher. This
is what you, Jacob, wanted to do all along, and addresses point 1.

Thoughts?

Sounds mostly good to me. But I think we'd want to automatically
increase sslmode to require if it is unset, but sslnegotation is set
to direct. Similar to how we bump sslmode to verify-full if
sslrootcert is set to system, but sslmode is unset. i.e. it seems
unnecessary/unwanted to throw an error if the connection string only
contains sslnegotiation=direct

I find that error-prone. For example:

1. Try to connect to a server with direct negotiation: psql "host=foobar
dbname=mydb sslnegotiation=direct"

2. It fails. Maybe it was an old server? Let's change it to
sslnegotiation=postgres.

3. Now it succeeds. Great!

You might miss that by changing sslnegotiation to 'postgres', or by
removing it altogether, you not only made it compatible with older
server versions, but you also allowed falling back to a plaintext
connection. Maybe you're fine with that, but maybe not. I'd like to
nudge people to use sslmode=require, not rely on implicit stuff like
this just to make connection strings a little shorter.

I'm not a fan of sslrootcert=system implying sslmode=verify-full either,
for the same reasons. But at least "sslrootcert" is a clearly
security-related setting, so removing it might give you a pause, whereas
sslnegotition is about performance and compatibility.

In v18, I'd like to make sslmode=require the default. Or maybe introduce
a new setting like "encryption=ssl|gss|none", defaulting to 'ssl'. If we
want to encourage encryption, that's the right way to do it. (I'd still
recommend everyone to use an explicit sslmode=require in their
connection strings for many years, though, because you might be using an
older client without realizing it.)

--
Heikki Linnakangas
Neon (https://neon.tech)

#39Jelte Fennema-Nio
postgres@jeltef.nl
In reply to: Heikki Linnakangas (#38)
Re: Direct SSL connection with ALPN and HBA rules

On Sun, 12 May 2024 at 23:39, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

You might miss that by changing sslnegotiation to 'postgres', or by
removing it altogether, you not only made it compatible with older
server versions, but you also allowed falling back to a plaintext
connection. Maybe you're fine with that, but maybe not. I'd like to
nudge people to use sslmode=require, not rely on implicit stuff like
this just to make connection strings a little shorter.

I understand your worry, but I'm not sure that this is actually much
of a security issue in practice. sslmode=prefer and sslmode=require
are the same amount of insecure imho (i.e. extremely insecure). The
only reason sslmode=prefer would connect as non-ssl to a server that
supports ssl is if an attacker has write access to the network in the
middle (i.e. eavesdropping ability alone is not enough). Once an
attacker has this level of network access, it's trivial for this
attacker to read any data sent to Postgres by intercepting the TLS
handshake and doing TLS termination with some arbitrary cert (any cert
is trusted by sslmode=require).

So the only actual case where this is a security issue I can think of
is when an attacker has only eavesdropping ability on the network. And
somehow the Postgres server that the client tries to connect to is
configured incorrectly, so that no ssl is set up at all. Then a client
would drop to plaintext, when connecting to this server instead of
erroring, and the attacker could now read the traffic. But I don't
really see this scenario end up any differently when requiring people
to enter sslmode=require. The only action a user can take to connect
to a server that does not have ssl support is to remove
sslmode=require from the connection string. Except if they are also
the server operator, in which case they could enable ssl on the
server. But if ssl is not set up, then it was probably never set up,
and thus providing sslnegotiation=direct would be to test if ssl
works.

But, if you disagree I'm fine with erroring on plain sslnegotiation=direct

In v18, I'd like to make sslmode=require the default. Or maybe introduce
a new setting like "encryption=ssl|gss|none", defaulting to 'ssl'. If we
want to encourage encryption, that's the right way to do it. (I'd still
recommend everyone to use an explicit sslmode=require in their
connection strings for many years, though, because you might be using an
older client without realizing it.)

I'm definitely a huge proponent of making the connection defaults more
secure. But as described above sslmode=require is still extremely
insecure and I don't think we gain anything substantial by making it
the default. I think the only useful secure default would be to use
sslmode=verify-full (with probably some automatic fallback to
sslmode=prefer when connecting to hardcoded IPs or localhost). Which
probably means that sslrootcert=system should also be made the
default. Which would mean that ~/.postgresql/root.crt would not be the
default anymore, which I personally think is fine but others likely
disagree.

#40Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Jelte Fennema-Nio (#39)
Re: Direct SSL connection with ALPN and HBA rules

On 13/05/2024 12:50, Jelte Fennema-Nio wrote:

On Sun, 12 May 2024 at 23:39, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

In v18, I'd like to make sslmode=require the default. Or maybe introduce
a new setting like "encryption=ssl|gss|none", defaulting to 'ssl'. If we
want to encourage encryption, that's the right way to do it. (I'd still
recommend everyone to use an explicit sslmode=require in their
connection strings for many years, though, because you might be using an
older client without realizing it.)

I'm definitely a huge proponent of making the connection defaults more
secure. But as described above sslmode=require is still extremely
insecure and I don't think we gain anything substantial by making it
the default. I think the only useful secure default would be to use
sslmode=verify-full (with probably some automatic fallback to
sslmode=prefer when connecting to hardcoded IPs or localhost). Which
probably means that sslrootcert=system should also be made the
default. Which would mean that ~/.postgresql/root.crt would not be the
default anymore, which I personally think is fine but others likely
disagree.

"channel_binding=require sslmode=require" also protects from MITM attacks.

I think these options should be designed from the user's point of view,
so that the user can specify the risks they're willing to accept, and
the details of how that's accomplished are handled by libpq. For
example, I'm OK with (tick all that apply):

[ ] passive eavesdroppers seeing all the traffic
[ ] MITM being able to hijack the session
[ ] connecting without verifying the server's identity
[ ] divulging the plaintext password to the server
[ ] ...

The requirements for whether SSL or GSS encryption is required, whether
the server's certificate needs to signed with known CA, etc. can be
derived from those. For example, if you need protection from
eavesdroppers, SSL or GSS encryption must be used. If you need to verify
the server's identity, it implies sslmode=verify-CA or
channel_binding=true. If you don't want to divulge the password, it
implies a suitable require_auth setting. I don't have a concrete
proposal yet, but something like that. And the defaults for those are up
for debate.

psql could perhaps help by listing the above properties at the beginning
of the session, something like:

psql (16.2)
WARNING: Connection is not encrypted.
WARNING: The server's identity has not been verified
Type "help" for help.

postgres=#

Although for the "divulge plaintext password to server" property, it's
too late to print a warning after connecting, because the damage has
already been done.

A different line of thought is that to keep the attack surface as smal
as possible, you should specify very explicitly what exactly you expect
to happen in the authentication, and disallow any variance. For example,
you expect SCRAM to be used, with a certificate signed by a particular
CA, and if the server requests anything else, that's suspicious and the
connection is aborted. We should make that possible too, but the above
flexible risk-based approach seems good for the defaults.

--
Heikki Linnakangas
Neon (https://neon.tech)

#41Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Heikki Linnakangas (#36)
1 attachment(s)
Re: Direct SSL connection with ALPN and HBA rules

On 10/05/2024 16:50, Heikki Linnakangas wrote:

New proposal:

- Remove the "try both" mode completely, and rename "requiredirect" to
just "direct". So there would be just two modes: "postgres" and
"direct". On reflection, the automatic fallback mode doesn't seem very
useful. It would make sense as the default, because then you would get
the benefits automatically in most cases but still be compatible with
old servers. But if it's not the default, you have to fiddle with libpq
settings anyway to enable it, and then you might as well use the
"requiredirect" mode when you know the server supports it. There isn't
anything wrong with it as such, but given how much confusion there's
been on how this all works, I'd prefer to cut this back to the bare
minimum now. We can add it back in the future, and perhaps make it the
default at the same time. This addresses points 2. and 3. above.

and:

- Only allow sslnegotiation=direct with sslmode=require or higher. This
is what you, Jacob, wanted to do all along, and addresses point 1.

Here's a patch to implement that.

--
Heikki Linnakangas
Neon (https://neon.tech)

Attachments:

0001-Remove-option-to-fall-back-from-direct-to-postgres-S.patchtext/x-patch; charset=UTF-8; name=0001-Remove-option-to-fall-back-from-direct-to-postgres-S.patchDownload
From 2a7bde8502966a634b62d4109f0cde5fdc685da2 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 13 May 2024 16:36:54 +0300
Subject: [PATCH 1/1] Remove option to fall back from direct to postgres SSL
 negotiation

There were three problems with the sslnegotiation options:

1. The sslmode=prefer and sslnegotiation=requiredirect combination was
somewhat dangerous, as you might unintentionally fall back to
plaintext authentication when connecting to a pre-v17 server.

2. There was an asymmetry between 'postgres' and 'direct'
options. 'postgres' meant "try only traditional negotiation", while
'direct' meant "try direct first, and fall back to traditional
negotiation if it fails". That was apparent only if you knew that the
'requiredirect' mode also exists.

3. The "require" word in 'requiredirect' suggests that it's somehow
more strict or more secure, similar to sslmode. However, I don't
consider direct SSL connections to be a security feature.

To address these problems:

- Only allow sslnegotiation='direct' if sslmode='require' or
stronger. And for the record, Jacob and Robert felt that we should do
that (or have sslnegotiation='direct' imply sslmode='require') anyway,
regardless of the first issue.

- Remove the 'direct' mode that falls back to traditional negotiation,
and rename what was called 'requiredirect' to 'direct' instead. In
other words, there is no "try both methods" option anymore, 'postgres'
now means the traditional negotiation and 'direct' means a direct SSL
connection.

Discussion: https://www.postgresql.org/message-id/d3b1608a-a1b6-4eda-9ec5-ddb3e4375808%40iki.fi
---
 doc/src/sgml/libpq.sgml                       |  49 ++--
 src/interfaces/libpq/fe-connect.c             |  52 ++--
 src/interfaces/libpq/libpq-int.h              |   3 +-
 .../libpq/t/005_negotiate_encryption.pl       | 254 ++++++++----------
 4 files changed, 150 insertions(+), 208 deletions(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 1d32c226d8..b32e497b1b 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1772,15 +1772,18 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       <term><literal>sslnegotiation</literal></term>
       <listitem>
        <para>
-        This option controls whether <productname>PostgreSQL</productname>
-        will perform its protocol negotiation to request encryption from the
-        server or will just directly make a standard <acronym>SSL</acronym>
-        connection.  Traditional <productname>PostgreSQL</productname>
-        protocol negotiation is the default and the most flexible with
-        different server configurations. If the server is known to support
-        direct <acronym>SSL</acronym> connections then the latter requires one
-        fewer round trip reducing connection latency and also allows the use
-        of protocol agnostic SSL network tools.
+        This option controls how SSL encryption is negotiated with the server,
+        if SSL is used. In the default <literal>postgres</literal> mode, the
+        client first asks the server if SSL is supported. In
+        <literal>direct</literal> mode, the client starts the standard SSL
+        handshake directly after establishing the TCP/IP connection. Traditional
+        <productname>PostgreSQL</productname> protocol negotiation is the most
+        flexible with different server configurations. If the server is known
+        to support direct <acronym>SSL</acronym> connections then the latter
+        requires one fewer round trip reducing connection latency and also
+        allows the use of protocol agnostic SSL network tools. The direct SSL
+        option was introduced in <productname>PostgreSQL</productname> version
+        17.
        </para>
 
         <variablelist>
@@ -1798,32 +1801,14 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
           <term><literal>direct</literal></term>
           <listitem>
            <para>
-             first attempt to establish a standard SSL connection and if that
-             fails reconnect and perform the negotiation. This fallback
-             process adds significant latency if the initial SSL connection
-             fails.
-           </para>
-           <para>
-             An exception is if <literal>gssencmode</literal> is set
-             to <literal>prefer</literal>, but the server rejects GSS encryption.
-             In that case, SSL is negotiated over the same TCP connection using
-             <productname>PostgreSQL</productname> protocol negotiation. In
-             other words, the direct SSL handshake is not used, if a TCP
-             connection has already been established and can be used for the
+             start SSL handshake directly after establishing the TCP/IP
+             connection.  This is only allowed with sslmode=require or higher,
+             because the weaker settings could lead to unintended fallback to
+             plaintext authentication when the server does not support direct
              SSL handshake.
            </para>
           </listitem>
          </varlistentry>
-
-         <varlistentry>
-          <term><literal>requiredirect</literal></term>
-          <listitem>
-           <para>
-             attempt to establish a standard SSL connection and if that fails
-             return a connection failure immediately.
-           </para>
-          </listitem>
-         </varlistentry>
         </variablelist>
       </listitem>
      </varlistentry>
@@ -2064,7 +2049,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         connections without having to decrypt the SSL stream.  (Note that
         unless the proxy is aware of the PostgreSQL protocol handshake this
         would require setting <literal>sslnegotiation</literal>
-        to <literal>direct</literal> or <literal>requiredirect</literal>.)
+        to <literal>direct</literal>.)
         However, <acronym>SNI</acronym> makes the destination host name appear
         in cleartext in the network traffic, so it might be undesirable in
         some cases.
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 81d278c395..4a5a71de4b 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -274,7 +274,7 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 	offsetof(struct pg_conn, sslmode)},
 
 	{"sslnegotiation", "PGSSLNEGOTIATION", DefaultSSLNegotiation, NULL,
-		"SSL-Negotiation", "", 14,	/* sizeof("requiredirect") == 14 */
+		"SSL-Negotiation", "", 9,	/* sizeof("postgres") == 9  */
 	offsetof(struct pg_conn, sslnegotiation)},
 
 	{"sslcompression", "PGSSLCOMPRESSION", "0", NULL,
@@ -1590,8 +1590,7 @@ pqConnectOptions2(PGconn *conn)
 	if (conn->sslnegotiation)
 	{
 		if (strcmp(conn->sslnegotiation, "postgres") != 0
-			&& strcmp(conn->sslnegotiation, "direct") != 0
-			&& strcmp(conn->sslnegotiation, "requiredirect") != 0)
+			&& strcmp(conn->sslnegotiation, "direct") != 0)
 		{
 			conn->status = CONNECTION_BAD;
 			libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
@@ -1608,6 +1607,25 @@ pqConnectOptions2(PGconn *conn)
 			return false;
 		}
 #endif
+
+		/*
+		 * Don't allow direct SSL negotiation with sslmode='prefer', because
+		 * that poses a risk of unintentional fallback to plaintext connection
+		 * when connecting to a pre-v17 server that does not support direct
+		 * SSL connections. To keep things simple, don't allow it with
+		 * sslmode='allow' or sslmode='disable' either. If a user goes through
+		 * the trouble of setting sslnegotiation='direct', they probably
+		 * intend to use SSL, and sslmode=disable or allow is probably a user
+		 * user mistake anyway.
+		 */
+		if (conn->sslnegotiation[0] == 'd' &&
+			conn->sslmode[0] != 'r' && conn->sslmode[0] != 'v')
+		{
+			conn->status = CONNECTION_BAD;
+			libpq_append_conn_error(conn, "weak sslmode \"%s\" may not be used with sslnegotiation=direct (use \"require\", \"verify-ca\", or \"verify-full\")",
+									conn->sslmode);
+			return false;
+		}
 	}
 	else
 	{
@@ -4312,8 +4330,6 @@ init_allowed_encryption_methods(PGconn *conn)
 		if (conn->sslnegotiation[0] == 'p')
 			conn->allowed_enc_methods |= ENC_NEGOTIATED_SSL;
 		else if (conn->sslnegotiation[0] == 'd')
-			conn->allowed_enc_methods |= ENC_DIRECT_SSL | ENC_NEGOTIATED_SSL;
-		else if (conn->sslnegotiation[0] == 'r')
 			conn->allowed_enc_methods |= ENC_DIRECT_SSL;
 	}
 #endif
@@ -4376,18 +4392,6 @@ connection_failed(PGconn *conn)
 	Assert((conn->failed_enc_methods & conn->current_enc_method) == 0);
 	conn->failed_enc_methods |= conn->current_enc_method;
 
-	/*
-	 * If the server reported an error after the SSL handshake, no point in
-	 * retrying with negotiated vs direct SSL.
-	 */
-	if ((conn->current_enc_method & (ENC_DIRECT_SSL | ENC_NEGOTIATED_SSL)) != 0 &&
-		conn->ssl_handshake_started)
-	{
-		conn->failed_enc_methods |= (ENC_DIRECT_SSL | ENC_NEGOTIATED_SSL) & conn->allowed_enc_methods;
-	}
-	else
-		conn->failed_enc_methods |= conn->current_enc_method;
-
 	return select_next_encryption_method(conn, false);
 }
 
@@ -4449,20 +4453,8 @@ select_next_encryption_method(PGconn *conn, bool have_valid_connection)
 	if (conn->sslmode[0] == 'a')
 		SELECT_NEXT_METHOD(ENC_PLAINTEXT);
 
-	/*
-	 * If enabled, try direct SSL. Unless we have a valid TCP connection that
-	 * failed negotiating GSSAPI encryption; in that case we prefer to reuse
-	 * the connection with negotiated SSL, instead of reconnecting to do
-	 * direct SSL. The point of sslnegotiation=direct is to avoid the
-	 * roundtrip from the negotiation, but reconnecting would also incur a
-	 * roundtrip. (In sslnegotiation=requiredirect mode, negotiated SSL is not
-	 * in the list of allowed methods and we will reconnect.)
-	 */
-	if (have_valid_connection)
-		SELECT_NEXT_METHOD(ENC_NEGOTIATED_SSL);
-
-	SELECT_NEXT_METHOD(ENC_DIRECT_SSL);
 	SELECT_NEXT_METHOD(ENC_NEGOTIATED_SSL);
+	SELECT_NEXT_METHOD(ENC_DIRECT_SSL);
 
 	if (conn->sslmode[0] != 'a')
 		SELECT_NEXT_METHOD(ENC_PLAINTEXT);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 3691e5ee96..10d2709869 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -395,8 +395,7 @@ struct pg_conn
 	char	   *keepalives_count;	/* maximum number of TCP keepalive
 									 * retransmits */
 	char	   *sslmode;		/* SSL mode (require,prefer,allow,disable) */
-	char	   *sslnegotiation; /* SSL initiation style
-								 * (postgres,direct,requiredirect) */
+	char	   *sslnegotiation; /* SSL initiation style (postgres,direct) */
 	char	   *sslcompression; /* SSL compression (0 or 1) */
 	char	   *sslkey;			/* client key filename */
 	char	   *sslcert;		/* client certificate filename */
diff --git a/src/interfaces/libpq/t/005_negotiate_encryption.pl b/src/interfaces/libpq/t/005_negotiate_encryption.pl
index 03a9079f3f..36ba8aede8 100644
--- a/src/interfaces/libpq/t/005_negotiate_encryption.pl
+++ b/src/interfaces/libpq/t/005_negotiate_encryption.pl
@@ -209,7 +209,7 @@ $node->reload;
 my @all_test_users = ('testuser', 'ssluser', 'nossluser', 'gssuser', 'nogssuser');
 my @all_gssencmodes = ('disable', 'prefer', 'require');
 my @all_sslmodes = ('disable', 'allow', 'prefer', 'require');
-my @all_sslnegotiations = ('postgres', 'direct', 'requiredirect');
+my @all_sslnegotiations = ('postgres', 'direct');
 
 my $server_config = {
 	server_ssl => 0,
@@ -223,23 +223,22 @@ my $test_table;
 if ($ssl_supported) {
 	$test_table = q{
 # USER      GSSENCMODE   SSLMODE      SSLNEGOTIATION EVENTS                      -> OUTCOME
-testuser    disable      disable      *              connect, authok             -> plain
-.           .            allow        *              connect, authok             -> plain
+testuser    disable      disable      postgres       connect, authok             -> plain
+.           .            allow        postgres       connect, authok             -> plain
 .           .            prefer       postgres       connect, sslreject, authok  -> plain
-.           .            .            direct         connect, directsslreject, reconnect, sslreject, authok  -> plain
-.           .            .            requiredirect  connect, directsslreject, reconnect, authok -> plain
 .           .            require      postgres       connect, sslreject          -> fail
-.           .            .            direct         connect, directsslreject, reconnect, sslreject -> fail
-.           .            .            requiredirect  connect, directsslreject    -> fail
-.           prefer       disable      *              connect, authok             -> plain
-.           .            allow        *              connect, authok             -> plain
+.           .            .            direct         connect, directsslreject    -> fail
+.           prefer       disable      postgres       connect, authok             -> plain
+.           .            allow        postgres       connect, authok             -> plain
 .           .            prefer       postgres       connect, sslreject, authok  -> plain
-.           .            .            direct         connect, directsslreject, reconnect, sslreject, authok  -> plain
-.           .            .            requiredirect  connect, directsslreject, reconnect, authok -> plain
 .           .            require      postgres       connect, sslreject          -> fail
-.           .            .            direct         connect, directsslreject, reconnect, sslreject -> fail
-.           .            .            requiredirect  connect, directsslreject    -> fail
-	};
+.           .            .            direct         connect, directsslreject    -> fail
+
+# sslnegotiation=direct is not acccepted unless sslmode=require or stronger
+*           *            disable      direct         -     -> fail
+*           *            allow        direct         -     -> fail
+*           *            prefer       direct         -     -> fail
+};
 } else {
 	# Compiled without SSL support
 	$test_table = q{
@@ -251,11 +250,10 @@ testuser    disable      disable      postgres       connect, authok
 .           .            allow        postgres       connect, authok             -> plain
 .           .            prefer       postgres       connect, authok             -> plain
 
-# Without SSL support, sslmode=require and sslnegotiation=direct/requiredirect
-# are not accepted at all.
-.           *            require      *              -     -> fail
-.           *            *            direct         -     -> fail
-.           *            *            requiredirect  -     -> fail
+# Without SSL support, sslmode=require and sslnegotiation=direct are
+# not accepted at all
+*           *            require      *              -     -> fail
+*           *            *            direct         -     -> fail
 	};
 }
 
@@ -281,34 +279,26 @@ SKIP:
 
 	$test_table = q{
 # USER      GSSENCMODE   SSLMODE      SSLNEGOTIATION EVENTS                                          -> OUTCOME
-testuser    disable      disable      *              connect, authok                                 -> plain
-.           .            allow        *              connect, authok                                 -> plain
+testuser    disable      disable      postgres       connect, authok                                 -> plain
+.           .            allow        postgres       connect, authok                                 -> plain
 .           .            prefer       postgres       connect, sslaccept, authok                      -> ssl
-.           .            .            direct         connect, directsslaccept, authok                -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok                -> ssl
 .           .            require      postgres       connect, sslaccept, authok                      -> ssl
 .           .            .            direct         connect, directsslaccept, authok                -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok                -> ssl
-ssluser     .            disable      *              connect, authfail                               -> fail
+ssluser     .            disable      postgres       connect, authfail                               -> fail
 .           .            allow        postgres       connect, authfail, reconnect, sslaccept, authok -> ssl
-.           .            .            direct         connect, authfail, reconnect, directsslaccept, authok -> ssl
-.           .            .            requiredirect  connect, authfail, reconnect, directsslaccept, authok -> ssl
 .           .            prefer       postgres       connect, sslaccept, authok                      -> ssl
-.           .            .            direct         connect, directsslaccept, authok                -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok                -> ssl
 .           .            require      postgres       connect, sslaccept, authok                      -> ssl
 .           .            .            direct         connect, directsslaccept, authok                -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok                -> ssl
-nossluser   .            disable      *              connect, authok                                 -> plain
+nossluser   .            disable      postgres       connect, authok                                 -> plain
 .           .            allow        postgres       connect, authok                                 -> plain
-.           .            .            direct         connect, authok                                 -> plain
-.           .            .            requiredirect  connect, authok                                 -> plain
 .           .            prefer       postgres       connect, sslaccept, authfail, reconnect, authok -> plain
-.           .            .            direct         connect, directsslaccept, authfail, reconnect, authok -> plain
-.           .            .            requiredirect  connect, directsslaccept, authfail, reconnect, authok -> plain
 .           .            require      postgres       connect, sslaccept, authfail                    -> fail
 .           .            require      direct         connect, directsslaccept, authfail              -> fail
-.           .            require      requiredirect  connect, directsslaccept, authfail              -> fail
+
+# sslnegotiation=direct is not acccepted unless sslmode=require or stronger
+*           *            disable      direct         -     -> fail
+*           *            allow        direct         -     -> fail
+*           *            prefer       direct         -     -> fail
 };
 
 	# Enable SSL in the server
@@ -342,62 +332,54 @@ SKIP:
 
 	$test_table = q{
 # USER      GSSENCMODE   SSLMODE      SSLNEGOTIATION EVENTS                       -> OUTCOME
-testuser    disable      disable      *              connect, authok              -> plain
-.           .            allow        *              connect, authok              -> plain
+testuser    disable      disable      postgres       connect, authok              -> plain
+.           .            allow        postgres       connect, authok              -> plain
 .           .            prefer       postgres       connect, sslreject, authok   -> plain
-.           .            .            direct         connect, directsslreject, reconnect, sslreject, authok  -> plain
-.           .            .            requiredirect  connect, directsslreject, reconnect, authok             -> plain
 .           .            require      postgres       connect, sslreject                -> fail
-.           .            .            direct         connect, directsslreject, reconnect, sslreject -> fail
-.           .            .            requiredirect  connect, directsslreject          -> fail
-.           prefer       *            *              connect, gssaccept, authok        -> gss
-.           require      *            *              connect, gssaccept, authok        -> gss
+.           .            .            direct         connect, directsslreject          -> fail
+.           prefer       *            postgres       connect, gssaccept, authok        -> gss
+.           prefer       require      direct         connect, gssaccept, authok        -> gss
+.           require      *            postgres       connect, gssaccept, authok        -> gss
+.           .            require      direct         connect, gssaccept, authok        -> gss
 
-gssuser     disable      disable      *              connect, authfail                  -> fail
+gssuser     disable      disable      postgres       connect, authfail                  -> fail
 .           .            allow        postgres       connect, authfail, reconnect, sslreject -> fail
-.           .            .            direct         connect, authfail, reconnect, directsslreject, reconnect, sslreject -> fail
-.           .            .            requiredirect  connect, authfail, reconnect, directsslreject -> fail
 .           .            prefer       postgres       connect, sslreject, authfail       -> fail
-.           .            .            direct         connect, directsslreject, reconnect, sslreject, authfail -> fail
-.           .            .            requiredirect  connect, directsslreject, reconnect, authfail -> fail
 .           .            require      postgres       connect, sslreject                 -> fail
-.           .            .            direct         connect, directsslreject, reconnect, sslreject -> fail
-.           .            .            requiredirect  connect, directsslreject           -> fail
-.           prefer       *            *              connect, gssaccept, authok   -> gss
-.           require      *            *              connect, gssaccept, authok   -> gss
+.           .            .            direct         connect, directsslreject           -> fail
+.           prefer       *            postgres       connect, gssaccept, authok   -> gss
+.           prefer       require      direct         connect, gssaccept, authok   -> gss
+.           require      *            postgres       connect, gssaccept, authok   -> gss
+.           .            require      direct         connect, gssaccept, authok   -> gss
 
-nogssuser   disable      disable      *              connect, authok              -> plain
+nogssuser   disable      disable      postgres       connect, authok              -> plain
 .           .            allow        postgres       connect, authok              -> plain
-.           .            .            direct         connect, authok              -> plain
-.           .            .            requiredirect  connect, authok              -> plain
 .           .            prefer       postgres       connect, sslreject, authok   -> plain
-.           .            .            direct         connect, directsslreject, reconnect, sslreject, authok   -> plain
-.           .            .            requiredirect  connect, directsslreject, reconnect, authok              -> plain
 .           .            require      postgres       connect, sslreject                 -> fail
-.           .            .            direct         connect, directsslreject, reconnect, sslreject -> fail
-.           .            .            requiredirect  connect, directsslreject           -> fail
-.           prefer       disable      *              connect, gssaccept, authfail, reconnect, authok             -> plain
+.           .            .            direct         connect, directsslreject           -> fail
+.           prefer       disable      postgres       connect, gssaccept, authfail, reconnect, authok             -> plain
 .           .            allow        postgres       connect, gssaccept, authfail, reconnect, authok             -> plain
-.           .            .            direct         connect, gssaccept, authfail, reconnect, authok             -> plain
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, authok             -> plain
 .           .            prefer       postgres       connect, gssaccept, authfail, reconnect, sslreject, authok  -> plain
-.           .            .            direct         connect, gssaccept, authfail, reconnect, directsslreject, reconnect, sslreject, authok  -> plain
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, directsslreject, reconnect, authok  -> plain
 .           .            require      postgres       connect, gssaccept, authfail, reconnect, sslreject          -> fail
-.           .            .            direct         connect, gssaccept, authfail, reconnect, directsslreject, reconnect, sslreject          -> fail
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, directsslreject          -> fail
-.           require      disable      *              connect, gssaccept, authfail -> fail
-.           .            allow        *              connect, gssaccept, authfail -> fail
-.           .            prefer       *              connect, gssaccept, authfail -> fail
-.           .            require      *              connect, gssaccept, authfail -> fail   # If both GSSAPI and sslmode are required, and GSS is not available -> fail
+.           .            .            direct         connect, gssaccept, authfail, reconnect, directsslreject          -> fail
+.           require      disable      postgres       connect, gssaccept, authfail -> fail
+.           .            allow        postgres       connect, gssaccept, authfail -> fail
+.           .            prefer       postgres       connect, gssaccept, authfail -> fail
+.           .            require      postgres       connect, gssaccept, authfail -> fail   # If both GSSAPI and sslmode are required, and GSS is not available -> fail
+.           .            .            direct         connect, gssaccept, authfail -> fail   # If both GSSAPI and sslmode are required, and GSS is not available -> fail
+
+# sslnegotiation=direct is not acccepted unless sslmode=require or stronger
+*           *            disable      direct         -     -> fail
+*           *            allow        direct         -     -> fail
+*           *            prefer       direct         -     -> fail
 	};
 
 	# The expected events and outcomes above assume that SSL support
 	# is enabled. When libpq is compiled without SSL support, all
 	# attempts to connect with sslmode=require or
-	# sslnegotiation=direct/requiredirect would fail immediately without
-	# even connecting to the server. Skip those, because we tested
-	# them earlier already.
+	# sslnegotiation=direct would fail immediately without even
+	# connecting to the server. Skip those, because we tested them
+	# earlier already.
 	my ($sslmodes, $sslnegotiations);
 	if ($ssl_supported != 0) {
 		($sslmodes, $sslnegotiations) = (\@all_sslmodes, \@all_sslnegotiations);
@@ -431,100 +413,84 @@ SKIP:
 
 	$test_table = q{
 # USER      GSSENCMODE   SSLMODE      SSLNEGOTIATION EVENTS                       -> OUTCOME
-testuser    disable      disable      *              connect, authok              -> plain
-.           .            allow        *              connect, authok              -> plain
-.           .            prefer       postgres       connect, sslaccept, authok         -> ssl
-.           .            .            direct         connect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok   -> ssl
-.           .            require      postgres       connect, sslaccept, authok         -> ssl
+testuser    disable      disable      postgres       connect, authok              -> plain
+.           .            allow        postgres       connect, authok              -> plain
+.           .            prefer       postgres       connect, sslaccept, authok   -> ssl
+.           .            require      postgres       connect, sslaccept, authok   -> ssl
 .           .            .            direct         connect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok   -> ssl
-.           prefer       disable      *              connect, gssaccept, authok   -> gss
-.           .            allow        *              connect, gssaccept, authok   -> gss
-.           .            prefer       *              connect, gssaccept, authok   -> gss
-.           .            require      *              connect, gssaccept, authok   -> gss     # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require
-.           require      disable      *              connect, gssaccept, authok   -> gss
-.           .            allow        *              connect, gssaccept, authok   -> gss
-.           .            prefer       *              connect, gssaccept, authok   -> gss
-.           .            require      *              connect, gssaccept, authok   -> gss     # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require
-
-gssuser     disable      disable      *              connect, authfail            -> fail
+.           prefer       disable      postgres       connect, gssaccept, authok   -> gss
+.           .            allow        postgres       connect, gssaccept, authok   -> gss
+.           .            prefer       postgres       connect, gssaccept, authok   -> gss
+.           .            require      postgres       connect, gssaccept, authok   -> gss     # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require
+.           .            .            direct         connect, gssaccept, authok   -> gss
+.           require      disable      postgres       connect, gssaccept, authok   -> gss
+.           .            allow        postgres       connect, gssaccept, authok   -> gss
+.           .            prefer       postgres       connect, gssaccept, authok   -> gss
+.           .            require      postgres       connect, gssaccept, authok   -> gss     # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require
+.           .            .            direct         connect, gssaccept, authok   -> gss
+
+gssuser     disable      disable      postgres       connect, authfail            -> fail
 .           .            allow        postgres       connect, authfail, reconnect, sslaccept, authfail -> fail
-.           .            .            direct         connect, authfail, reconnect, directsslaccept, authfail -> fail
-.           .            .            requiredirect  connect, authfail, reconnect, directsslaccept, authfail -> fail
 .           .            prefer       postgres       connect, sslaccept, authfail, reconnect, authfail -> fail
-.           .            .            direct         connect, directsslaccept, authfail, reconnect, authfail -> fail
-.           .            .            requiredirect  connect, directsslaccept, authfail, reconnect, authfail -> fail
 .           .            require      postgres       connect, sslaccept, authfail       -> fail
 .           .            .            direct         connect, directsslaccept, authfail -> fail
-.           .            .            requiredirect  connect, directsslaccept, authfail -> fail
-.           prefer       disable      *              connect, gssaccept, authok   -> gss
-.           .            allow        *              connect, gssaccept, authok   -> gss
-.           .            prefer       *              connect, gssaccept, authok   -> gss
-.           .            require      *              connect, gssaccept, authok   -> gss   # GSS is chosen over SSL, even though sslmode=require
-.           require      disable      *              connect, gssaccept, authok   -> gss
-.           .            allow        *              connect, gssaccept, authok   -> gss
-.           .            prefer       *              connect, gssaccept, authok   -> gss
-.           .            require      *              connect, gssaccept, authok   -> gss     # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require
-
-ssluser     disable      disable      *              connect, authfail            -> fail
+.           prefer       disable      postgres       connect, gssaccept, authok   -> gss
+.           .            allow        postgres       connect, gssaccept, authok   -> gss
+.           .            prefer       postgres       connect, gssaccept, authok   -> gss
+.           .            require      postgres       connect, gssaccept, authok   -> gss   # GSS is chosen over SSL, even though sslmode=require
+.           .            .            direct         connect, gssaccept, authok   -> gss
+.           require      disable      postgres       connect, gssaccept, authok   -> gss
+.           .            allow        postgres       connect, gssaccept, authok   -> gss
+.           .            prefer       postgres       connect, gssaccept, authok   -> gss
+.           .            require      postgres       connect, gssaccept, authok   -> gss     # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require
+.           .            .            direct         connect, gssaccept, authok   -> gss
+
+ssluser     disable      disable      postgres       connect, authfail            -> fail
 .           .            allow        postgres       connect, authfail, reconnect, sslaccept, authok       -> ssl
-.           .            .            direct         connect, authfail, reconnect, directsslaccept, authok -> ssl
-.           .            .            requiredirect  connect, authfail, reconnect, directsslaccept, authok -> ssl
 .           .            prefer       postgres       connect, sslaccept, authok         -> ssl
-.           .            .            direct         connect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok   -> ssl
 .           .            require      postgres       connect, sslaccept, authok         -> ssl
 .           .            .            direct         connect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok   -> ssl
-.           prefer       disable      *              connect, gssaccept, authfail, reconnect, authfail -> fail
+.           prefer       disable      postgres       connect, gssaccept, authfail, reconnect, authfail -> fail
 .           .            allow        postgres       connect, gssaccept, authfail, reconnect, authfail, reconnect, sslaccept, authok       -> ssl
-.           .            .            direct         connect, gssaccept, authfail, reconnect, authfail, reconnect, directsslaccept, authok -> ssl
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, authfail, reconnect, directsslaccept, authok -> ssl
 .           .            prefer       postgres       connect, gssaccept, authfail, reconnect, sslaccept, authok       -> ssl
-.           .            .            direct         connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl
 .           .            require      postgres       connect, gssaccept, authfail, reconnect, sslaccept, authok       -> ssl
 .           .            .            direct         connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl
-.           require      disable      *              connect, gssaccept, authfail -> fail
-.           .            allow        *              connect, gssaccept, authfail -> fail
-.           .            prefer       *              connect, gssaccept, authfail -> fail
-.           .            require      *              connect, gssaccept, authfail -> fail         # If both GSS and SSL are required, the sslmode=require is effectively ignored and GSS is required
+.           require      disable      postgres       connect, gssaccept, authfail -> fail
+.           .            allow        postgres       connect, gssaccept, authfail -> fail
+.           .            prefer       postgres       connect, gssaccept, authfail -> fail
+.           .            require      postgres       connect, gssaccept, authfail -> fail         # If both GSS and SSL are required, the sslmode=require is effectively ignored and GSS is required
+.           .            .            direct         connect, gssaccept, authfail -> fail
 
-nogssuser   disable      disable      *              connect, authok              -> plain
+nogssuser   disable      disable      postgres       connect, authok              -> plain
 .           .            allow        postgres       connect, authok              -> plain
-.           .            .            direct         connect, authok              -> plain
-.           .            .            requiredirect  connect, authok              -> plain
 .           .            prefer       postgres       connect, sslaccept, authok   -> ssl
-.           .            .            direct         connect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok   -> ssl
 .           .            require      postgres       connect, sslaccept, authok   -> ssl
 .           .            .            direct         connect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok   -> ssl
-.           prefer       disable      *              connect, gssaccept, authfail, reconnect, authok              -> plain
-.           .            allow        *              connect, gssaccept, authfail, reconnect, authok              -> plain
+.           prefer       disable      postgres       connect, gssaccept, authfail, reconnect, authok              -> plain
+.           .            allow        postgres       connect, gssaccept, authfail, reconnect, authok              -> plain
 .           .            prefer       postgres       connect, gssaccept, authfail, reconnect, sslaccept, authok         -> ssl
-.           .            .            direct         connect, gssaccept, authfail, reconnect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, directsslaccept, authok   -> ssl
 .           .            require      postgres       connect, gssaccept, authfail, reconnect, sslaccept, authok         -> ssl
 .           .            .            direct         connect, gssaccept, authfail, reconnect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, directsslaccept, authok   -> ssl
-.           require      disable      *              connect, gssaccept, authfail -> fail
-.           .            allow        *              connect, gssaccept, authfail -> fail
-.           .            prefer       *              connect, gssaccept, authfail -> fail
-.           .            require      *              connect, gssaccept, authfail -> fail   # If both GSS and SSL are required, the sslmode=require is effectively ignored and GSS is required
-
-nossluser   disable      disable      *              connect, authok              -> plain
-.           .            allow        *              connect, authok              -> plain
+.           require      disable      postgres       connect, gssaccept, authfail -> fail
+.           .            allow        postgres       connect, gssaccept, authfail -> fail
+.           .            prefer       postgres       connect, gssaccept, authfail -> fail
+.           .            require      postgres       connect, gssaccept, authfail -> fail   # If both GSS and SSL are required, the sslmode=require is effectively ignored and GSS is required
+.           .            .            direct         connect, gssaccept, authfail -> fail
+
+nossluser   disable      disable      postgres       connect, authok              -> plain
+.           .            allow        postgres       connect, authok              -> plain
 .           .            prefer       postgres       connect, sslaccept, authfail, reconnect, authok       -> plain
-.           .            .            direct         connect, directsslaccept, authfail, reconnect, authok -> plain
-.           .            .            requiredirect  connect, directsslaccept, authfail, reconnect, authok -> plain
 .           .            require      postgres       connect, sslaccept, authfail       -> fail
 .           .            .            direct         connect, directsslaccept, authfail -> fail
-.           .            .            requiredirect  connect, directsslaccept, authfail -> fail
-.           prefer       *            *              connect, gssaccept, authok   -> gss
-.           require      *            *              connect, gssaccept, authok   -> gss
+.           prefer       *            postgres       connect, gssaccept, authok   -> gss
+.           .            require      direct         connect, gssaccept, authok   -> gss
+.           require      *            postgres       connect, gssaccept, authok   -> gss
+.           .            require      direct         connect, gssaccept, authok   -> gss
+
+# sslnegotiation=direct is not acccepted unless sslmode=require or stronger
+*           *            disable      direct         -     -> fail
+*           *            allow        direct         -     -> fail
+*           *            prefer       direct         -     -> fail
 	};
 
 	note("Running tests with both GSS and SSL enabled in server");
-- 
2.39.2

#42Jelte Fennema-Nio
postgres@jeltef.nl
In reply to: Heikki Linnakangas (#41)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, 13 May 2024 at 15:38, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Here's a patch to implement that.

+       if (conn->sslnegotiation[0] == 'd' &&
+           conn->sslmode[0] != 'r' && conn->sslmode[0] != 'v')

I think these checks should use strcmp instead of checking magic first
characters. I see this same clever trick is used in the recently added
init_allowed_encryption_methods, and I think that should be changed to
use strcmp too for readability.

#43Jelte Fennema-Nio
postgres@jeltef.nl
In reply to: Heikki Linnakangas (#40)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, 13 May 2024 at 13:07, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

"channel_binding=require sslmode=require" also protects from MITM attacks.

Cool, I didn't realize we had this connection option and it could be
used like this. But I think there's a few security downsides of
channel_binding=require over sslmode=verify-full: If the client relies
on channel_binding to validate server authenticity, a leaked
server-side SCRAM hash is enough for an attacker to impersonate a
server. While normally a leaked scram hash isn't really much of a
security concern (assuming long enough passwords). I also don't know
of many people rotating their scram hashes, even though many rotate
TLS certs.

I think these options should be designed from the user's point of view,
so that the user can specify the risks they're willing to accept, and
the details of how that's accomplished are handled by libpq. For
example, I'm OK with (tick all that apply):

[ ] passive eavesdroppers seeing all the traffic
[ ] MITM being able to hijack the session
[ ] connecting without verifying the server's identity
[ ] divulging the plaintext password to the server
[ ] ...

I think that sounds like a great idea, looking forward to the proposal.

#44Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Jelte Fennema-Nio (#42)
Re: Direct SSL connection with ALPN and HBA rules

On 13/05/2024 16:55, Jelte Fennema-Nio wrote:

On Mon, 13 May 2024 at 15:38, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Here's a patch to implement that.

+       if (conn->sslnegotiation[0] == 'd' &&
+           conn->sslmode[0] != 'r' && conn->sslmode[0] != 'v')

I think these checks should use strcmp instead of checking magic first
characters. I see this same clever trick is used in the recently added
init_allowed_encryption_methods, and I think that should be changed to
use strcmp too for readability.

Oh yeah, I hate that too. These should be refactored into enums, with a
clear separate stage of parsing the options from strings. But we use
that pattern all over the place, so I didn't want to start reforming it
with this patch.

--
Heikki Linnakangas
Neon (https://neon.tech)

#45Robert Haas
robertmhaas@gmail.com
In reply to: Heikki Linnakangas (#41)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, May 13, 2024 at 9:37 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 10/05/2024 16:50, Heikki Linnakangas wrote:

New proposal:

- Remove the "try both" mode completely, and rename "requiredirect" to
just "direct". So there would be just two modes: "postgres" and
"direct". On reflection, the automatic fallback mode doesn't seem very
useful. It would make sense as the default, because then you would get
the benefits automatically in most cases but still be compatible with
old servers. But if it's not the default, you have to fiddle with libpq
settings anyway to enable it, and then you might as well use the
"requiredirect" mode when you know the server supports it. There isn't
anything wrong with it as such, but given how much confusion there's
been on how this all works, I'd prefer to cut this back to the bare
minimum now. We can add it back in the future, and perhaps make it the
default at the same time. This addresses points 2. and 3. above.

and:

- Only allow sslnegotiation=direct with sslmode=require or higher. This
is what you, Jacob, wanted to do all along, and addresses point 1.

Here's a patch to implement that.

I find this idea to be a massive improvement over the status quo, and
I didn't spot any major problems when I read through the patch,
either. I'm not quite sure if the patch takes the right approach in
emphasizing that weaker sslmode settings are not allowed because of
unintended fallbacks. It seems to me that we could equally well say
that those combinations are nonsensical. If we're making a direct SSL
connection, SSL is eo ipso required.

I don't have a strong opinion about whether sslnegotiation=direct
should error out (as you propose here) or silently promote sslmode to
require. I think either is defensible. Had I been implementing it, I
think I would have done as Jacob proposes, just because once we've
forced a direct SSL negotiation surely the only sensible behavior is
to be using SSL, unless you think there should be a
silently-reconnect-without-SSL behavior, which I sure don't. However,
I disagree with Jacob's assertion that sslmode=require has no security
benefits over sslmode=prefer. That seems like the kind of pessimism
that makes people hate security professionals. There have got to be
some attacks that are foreclosed by encrypting the connection, even if
you don't stop MITM attacks or other things that are more
sophisticated than running wireshark and seeing what goes by on the
wire.

I'm pleased to hear that you will propose to make sslmode=require the
default in v18. I think we'll need to do some work to figure out how
much collateral damage that will cause, and maybe it will be more than
we can stomach, but Magnus has been saying for years that the current
default is terrible. I'm not sure I was entirely convinced of that the
first time I heard him say it, but I'm smarter now than I was then.
It's really hard to believe in 2024 that anyone should ever be using a
setting that may or may not encrypt the connection. There's http and
https but there's no httpmaybes.

--
Robert Haas
EDB: http://www.enterprisedb.com

#46Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Robert Haas (#45)
Re: Direct SSL connection with ALPN and HBA rules

(There's, uh, a lot to respond to above and I'm trying to figure out
how best to type up all of it.)

On Mon, May 13, 2024 at 9:13 AM Robert Haas <robertmhaas@gmail.com> wrote:

However,
I disagree with Jacob's assertion that sslmode=require has no security
benefits over sslmode=prefer.

For the record, I didn't say that... You mean Jelte's quote up above?:

sslmode=prefer and sslmode=require
are the same amount of insecure imho (i.e. extremely insecure).

I agree that requiring passive security is tangibly better than
allowing fallback to plaintext. I think Jelte's point might be better
stated as, =prefer and =require give the same amount of protection
against active attack (none).

--Jacob

#47Jelte Fennema-Nio
postgres@jeltef.nl
In reply to: Robert Haas (#45)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, 13 May 2024 at 18:14, Robert Haas <robertmhaas@gmail.com> wrote:

I disagree with Jacob's assertion that sslmode=require has no security
benefits over sslmode=prefer. That seems like the kind of pessimism
that makes people hate security professionals. There have got to be
some attacks that are foreclosed by encrypting the connection, even if
you don't stop MITM attacks or other things that are more
sophisticated than running wireshark and seeing what goes by on the
wire.

Like Jacob already said, I guess you meant me here. The main point I
was trying to make is that sslmode=require is extremely insecure too,
so if we're changing the default then I'd rather bite the bullet and
actually make the default a secure one this time. No-ones browser
trusts self-signed certs by default, but currently lots of people
trust self-signed certs when connecting to their production database
without realizing.

IMHO the only benefit that sslmode=require brings over sslmode=prefer
is detecting incorrectly configured servers i.e. servers that are
supposed to support ssl but somehow don't due to a misconfigured
GUC/pg_hba. Such "incorrectly configured server" detection avoids
sending data to such a server, which an eavesdropper on the network
could see. Which is definitely a security benefit, but it's an
extremely small one. In all other cases sslmode=prefer brings exactly
the same protection as sslmode=require, because sslmode=prefer
encrypts the connection unless postgres actively tells the client to
downgrade to plaintext (which never happens when the server is
configured correctly).

#48Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Heikki Linnakangas (#40)
On the use of channel binding without server certificates (was: Direct SSL connection with ALPN and HBA rules)

[soapbox thread, so I've changed the Subject]

On Mon, May 13, 2024 at 4:08 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

"channel_binding=require sslmode=require" also protects from MITM attacks.

This isn't true in the same way that "standard" TLS protects against
MITM. I know you know that, but for the benefit of bystanders reading
the threads, I think we should stop phrasing it like this. Most people
who want MITM protection need to be using verify-full.

Details for those bystanders: Channel binding alone will only
disconnect you after the MITM is discovered, after your startup packet
is leaked but before you send any queries to the server. A hash of
your password will also be leaked in that situation, which starts the
timer on an offline attack. And IIRC, you won't get an alert that says
"someone's in the middle"; it'll just look like you mistyped your
password.

(Stronger passwords provide stronger protection in this situation,
which is not a property that most people are used to. If I choose to
sign into Google with the password "hunter2", it doesn't somehow make
the TLS protection weaker. But if you rely on SCRAM by itself for
server authentication, it does more or less work like that.)

Use channel_binding *in addition to* sslmode=verify-full if you want
enhanced authentication of the peer, as suggested in the docs [1]https://www.postgresql.org/docs/current/preventing-server-spoofing.html.
Don't rely on channel binding alone for the vast majority of use
cases, and if you know better for your particular use case, then you
already know enough to be able to ignore my advice.

[/soapbox]

Thanks,
--Jacob

[1]: https://www.postgresql.org/docs/current/preventing-server-spoofing.html

#49Robert Haas
robertmhaas@gmail.com
In reply to: Jacob Champion (#46)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, May 13, 2024 at 12:45 PM Jacob Champion
<jacob.champion@enterprisedb.com> wrote:

For the record, I didn't say that... You mean Jelte's quote up above?

Yeah, sorry, I got my J-named hackers confused. Apologies.

--
Robert Haas
EDB: http://www.enterprisedb.com

#50Robert Haas
robertmhaas@gmail.com
In reply to: Jelte Fennema-Nio (#47)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, May 13, 2024 at 1:42 PM Jelte Fennema-Nio <postgres@jeltef.nl> wrote:

Like Jacob already said, I guess you meant me here. The main point I
was trying to make is that sslmode=require is extremely insecure too,
so if we're changing the default then I'd rather bite the bullet and
actually make the default a secure one this time. No-ones browser
trusts self-signed certs by default, but currently lots of people
trust self-signed certs when connecting to their production database
without realizing.

IMHO the only benefit that sslmode=require brings over sslmode=prefer
is detecting incorrectly configured servers i.e. servers that are
supposed to support ssl but somehow don't due to a misconfigured
GUC/pg_hba. Such "incorrectly configured server" detection avoids
sending data to such a server, which an eavesdropper on the network
could see. Which is definitely a security benefit, but it's an
extremely small one. In all other cases sslmode=prefer brings exactly
the same protection as sslmode=require, because sslmode=prefer
encrypts the connection unless postgres actively tells the client to
downgrade to plaintext (which never happens when the server is
configured correctly).

I think I agree with *nearly* every word of this. However:

(1) I don't want to hijack this thread about a v17 open item to talk
too much about a hypothetical v18 proposal.

(2) While in general you need more than just SSL to ensure security,
I'm not sure that there's only one way to do it, and I doubt that we
should try to pick a winner.

(3) I suspect that even going as far as sslmode=require by default is
going to be extremely painful for hackers, the project, and users.
Moving the goalposts further increases the likelihood of nothing
happening at all.

--
Robert Haas
EDB: http://www.enterprisedb.com

#51Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Robert Haas (#45)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, May 13, 2024 at 9:13 AM Robert Haas <robertmhaas@gmail.com> wrote:

I find this idea to be a massive improvement over the status quo,

+1

and
I didn't spot any major problems when I read through the patch,
either.

Definitely not a major problem, but I think
select_next_encryption_method() has gone stale, since it originally
provided generality and lines of fallback that no longer exist. In
other words, I think the following code is now misleading:

if (conn->sslmode[0] == 'a')
SELECT_NEXT_METHOD(ENC_PLAINTEXT);

SELECT_NEXT_METHOD(ENC_NEGOTIATED_SSL);
SELECT_NEXT_METHOD(ENC_DIRECT_SSL);

if (conn->sslmode[0] != 'a')
SELECT_NEXT_METHOD(ENC_PLAINTEXT);

To me, that implies that negotiated mode takes precedence over direct,
but the point of the patch is that it's not possible to have both. And
if direct SSL is in use, then sslmode can't be "allow" anyway, and we
definitely don't want ENC_PLAINTEXT.

So if someone proposes a change to select_next_encryption_method(),
you'll have to remember to stare at init_allowed_encryption_methods()
as well, and think really hard about what's going on. And vice-versa.
That worries me.

I don't have a strong opinion about whether sslnegotiation=direct
should error out (as you propose here) or silently promote sslmode to
require. I think either is defensible.

I'm comforted that, since sslrootcert=system already does it, plenty
of use cases will get that for free. And if you decide in the future
that you really really want it to promote, it won't be a compatibility
break to make that change. (That gives us more time for wider v16-17
adoption, to see how the sslrootcert=system magic promotion behavior
is going in practice.)

Had I been implementing it, I
think I would have done as Jacob proposes, just because once we've
forced a direct SSL negotiation surely the only sensible behavior is
to be using SSL, unless you think there should be a
silently-reconnect-without-SSL behavior, which I sure don't.

We still allow GSS to preempt SSL, though, so "forced" is probably
overstating things.

It's really hard to believe in 2024 that anyone should ever be using a
setting that may or may not encrypt the connection. There's http and
https but there's no httpmaybes.

+1. I think (someone hop in and correct me please) that Opportunistic
Encryption for HTTP mostly fizzled, and they gave it a *lot* of
thought.

--Jacob

#52Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Heikki Linnakangas (#40)
Re: Direct SSL connection with ALPN and HBA rules

[this should probably belong to a different thread, but I'm not sure
what to title it]

On Mon, May 13, 2024 at 4:08 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

I think these options should be designed from the user's point of view,
so that the user can specify the risks they're willing to accept, and
the details of how that's accomplished are handled by libpq. For
example, I'm OK with (tick all that apply):

[ ] passive eavesdroppers seeing all the traffic
[ ] MITM being able to hijack the session
[ ] connecting without verifying the server's identity
[ ] divulging the plaintext password to the server
[ ] ...

I'm pessimistic about a quality-of-protection scheme for this use case
(*). I don't think users need more knobs in their connection strings,
and many of the goals of transport encryption are really not
independent from each other in practice. As evidence I point to the
absolute mess of GSSAPI wrapping, which lets you check separate boxes
for things like "require the server to authenticate itself" and
"require integrity" and "allow MITMs to reorder messages" and so on,
as if the whole idea of "integrity" is useful if you don't know who
you're talking to in the first place. I think I recall slapd having
something similarly arcane (but at least it doesn't make the clients
do it!). Those kinds of APIs don't evolve well, in my opinion.

I think most people probably just want browser-grade security as
quickly and cheaply as possible, and we don't make that very easy
today. I'll try to review a QoP scheme if someone works on it, don't
get me wrong, but I'd much rather spend time on a "very secure by
default" mode that gets rid of most of the options (i.e. a
postgresqls:// scheme).

(*) I've proposed quality-of-protection in the past, for Postgres
proxy authentication [1]/messages/by-id/0768cedb-695a-8841-5f8b-da2aa64c8f3a@timescale.com. But I'm comfortable in my hypocrisy, because
in that case, the end user doing the configuration is a DBA with a
config file who is expected to understand the whole system, and it's a
niche use case (IMO) without an obvious "common setup". And frankly I
think my proposal is unlikely to go anywhere; the cost/benefit
probably isn't good enough.

If you need to verify
the server's identity, it implies sslmode=verify-CA or
channel_binding=true.

Neither of those two options provides strong authentication of the
peer, and personally I wouldn't want them to be considered worthy of
"verify the server's identity" mode.

And -- taking a wild leap here -- if we disagree, then granularity
becomes a problem: either the QoP scheme now has to have
sub-checkboxes for "if the server knows my password, that's good
enough" and "it's fine if the server's hostname doesn't match the
cert, for some reason", or it smashes all of those different ideas
into one setting and then I have to settle for the weakest common
denominator during an active attack. Assuming I haven't missed a third
option, will that be easier/better than the status quo of
require_auth+sslmode?

A different line of thought is that to keep the attack surface as smal
as possible, you should specify very explicitly what exactly you expect
to happen in the authentication, and disallow any variance. For example,
you expect SCRAM to be used, with a certificate signed by a particular
CA, and if the server requests anything else, that's suspicious and the
connection is aborted. We should make that possible too

That's 'require_auth=scram-sha-256 sslmode=verify-ca', no?

--Jacob

[1]: /messages/by-id/0768cedb-695a-8841-5f8b-da2aa64c8f3a@timescale.com

#53Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Jacob Champion (#28)
Re: Direct SSL connection with ALPN and HBA rules

On Mon, Apr 29, 2024 at 11:04 AM Jacob Champion
<jacob.champion@enterprisedb.com> wrote:

On Fri, Apr 26, 2024 at 3:51 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Unfortunately the error message you got in the client with that was
horrible (I modified the server to not accept the 'postgresql' protocol):

psql "dbname=postgres sslmode=require host=localhost"
psql: error: connection to server at "localhost" (::1), port 5432
failed: SSL error: SSL error code 167773280

<long sigh>

I filed a bug upstream [1].

I think this is on track to be fixed in a future set of OpenSSL 3.x
releases [2]https://github.com/openssl/openssl/pull/24351. We'll still need to carry the workaround while we
support 1.1.1.

--Jacob

[2]: https://github.com/openssl/openssl/pull/24351

#54Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Jacob Champion (#51)
1 attachment(s)
Re: Direct SSL connection with ALPN and HBA rules

On 14/05/2024 01:29, Jacob Champion wrote:

Definitely not a major problem, but I think
select_next_encryption_method() has gone stale, since it originally
provided generality and lines of fallback that no longer exist. In
other words, I think the following code is now misleading:

if (conn->sslmode[0] == 'a')
SELECT_NEXT_METHOD(ENC_PLAINTEXT);

SELECT_NEXT_METHOD(ENC_NEGOTIATED_SSL);
SELECT_NEXT_METHOD(ENC_DIRECT_SSL);

if (conn->sslmode[0] != 'a')
SELECT_NEXT_METHOD(ENC_PLAINTEXT);

To me, that implies that negotiated mode takes precedence over direct,
but the point of the patch is that it's not possible to have both. And
if direct SSL is in use, then sslmode can't be "allow" anyway, and we
definitely don't want ENC_PLAINTEXT.

So if someone proposes a change to select_next_encryption_method(),
you'll have to remember to stare at init_allowed_encryption_methods()
as well, and think really hard about what's going on. And vice-versa.
That worries me.

Ok, yeah, I can see that now. Here's a new version to address that. I
merged ENC_SSL_NEGOTIATED_SSL and ENC_SSL_DIRECT_SSL to a single method,
ENC_SSL. The places that need to distinguish between them now check
conn-sslnegotiation. That seems more clear now that there is no fallback.

--
Heikki Linnakangas
Neon (https://neon.tech)

Attachments:

v2-0001-Remove-option-to-fall-back-from-direct-to-postgre.patchtext/x-patch; charset=UTF-8; name=v2-0001-Remove-option-to-fall-back-from-direct-to-postgre.patchDownload
From 7a2bc2ede5ba7bef147e509ce3c4d5472c8e0247 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 15 May 2024 16:27:51 +0300
Subject: [PATCH v2 1/1] Remove option to fall back from direct to postgres SSL
 negotiation

There were three problems with the sslnegotiation options:

1. The sslmode=prefer and sslnegotiation=requiredirect combination was
somewhat dangerous, as you might unintentionally fall back to
plaintext authentication when connecting to a pre-v17 server.

2. There was an asymmetry between 'postgres' and 'direct'
options. 'postgres' meant "try only traditional negotiation", while
'direct' meant "try direct first, and fall back to traditional
negotiation if it fails". That was apparent only if you knew that the
'requiredirect' mode also exists.

3. The "require" word in 'requiredirect' suggests that it's somehow
more strict or more secure, similar to sslmode. However, I don't
consider direct SSL connections to be a security feature.

To address these problems:

- Only allow sslnegotiation='direct' if sslmode='require' or
stronger. And for the record, Jacob and Robert felt that we should do
that (or have sslnegotiation='direct' imply sslmode='require') anyway,
regardless of the first issue.

- Remove the 'direct' mode that falls back to traditional negotiation,
and rename what was called 'requiredirect' to 'direct' instead. In
other words, there is no "try both methods" option anymore, 'postgres'
now means the traditional negotiation and 'direct' means a direct SSL
connection.

Reviewed-by: Jelte Fennema-Nio, Robert Haas, Jacob Champion
Discussion: https://www.postgresql.org/message-id/d3b1608a-a1b6-4eda-9ec5-ddb3e4375808%40iki.fi
---
 doc/src/sgml/libpq.sgml                       |  49 ++--
 src/interfaces/libpq/fe-connect.c             | 144 +++++-----
 src/interfaces/libpq/fe-secure-openssl.c      |   2 +-
 src/interfaces/libpq/libpq-int.h              |   6 +-
 .../libpq/t/005_negotiate_encryption.pl       | 254 ++++++++----------
 5 files changed, 202 insertions(+), 253 deletions(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 1d32c226d8..b32e497b1b 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1772,15 +1772,18 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       <term><literal>sslnegotiation</literal></term>
       <listitem>
        <para>
-        This option controls whether <productname>PostgreSQL</productname>
-        will perform its protocol negotiation to request encryption from the
-        server or will just directly make a standard <acronym>SSL</acronym>
-        connection.  Traditional <productname>PostgreSQL</productname>
-        protocol negotiation is the default and the most flexible with
-        different server configurations. If the server is known to support
-        direct <acronym>SSL</acronym> connections then the latter requires one
-        fewer round trip reducing connection latency and also allows the use
-        of protocol agnostic SSL network tools.
+        This option controls how SSL encryption is negotiated with the server,
+        if SSL is used. In the default <literal>postgres</literal> mode, the
+        client first asks the server if SSL is supported. In
+        <literal>direct</literal> mode, the client starts the standard SSL
+        handshake directly after establishing the TCP/IP connection. Traditional
+        <productname>PostgreSQL</productname> protocol negotiation is the most
+        flexible with different server configurations. If the server is known
+        to support direct <acronym>SSL</acronym> connections then the latter
+        requires one fewer round trip reducing connection latency and also
+        allows the use of protocol agnostic SSL network tools. The direct SSL
+        option was introduced in <productname>PostgreSQL</productname> version
+        17.
        </para>
 
         <variablelist>
@@ -1798,32 +1801,14 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
           <term><literal>direct</literal></term>
           <listitem>
            <para>
-             first attempt to establish a standard SSL connection and if that
-             fails reconnect and perform the negotiation. This fallback
-             process adds significant latency if the initial SSL connection
-             fails.
-           </para>
-           <para>
-             An exception is if <literal>gssencmode</literal> is set
-             to <literal>prefer</literal>, but the server rejects GSS encryption.
-             In that case, SSL is negotiated over the same TCP connection using
-             <productname>PostgreSQL</productname> protocol negotiation. In
-             other words, the direct SSL handshake is not used, if a TCP
-             connection has already been established and can be used for the
+             start SSL handshake directly after establishing the TCP/IP
+             connection.  This is only allowed with sslmode=require or higher,
+             because the weaker settings could lead to unintended fallback to
+             plaintext authentication when the server does not support direct
              SSL handshake.
            </para>
           </listitem>
          </varlistentry>
-
-         <varlistentry>
-          <term><literal>requiredirect</literal></term>
-          <listitem>
-           <para>
-             attempt to establish a standard SSL connection and if that fails
-             return a connection failure immediately.
-           </para>
-          </listitem>
-         </varlistentry>
         </variablelist>
       </listitem>
      </varlistentry>
@@ -2064,7 +2049,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         connections without having to decrypt the SSL stream.  (Note that
         unless the proxy is aware of the PostgreSQL protocol handshake this
         would require setting <literal>sslnegotiation</literal>
-        to <literal>direct</literal> or <literal>requiredirect</literal>.)
+        to <literal>direct</literal>.)
         However, <acronym>SNI</acronym> makes the destination host name appear
         in cleartext in the network traffic, so it might be undesirable in
         some cases.
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 81d278c395..85f55b14a9 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -274,7 +274,7 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 	offsetof(struct pg_conn, sslmode)},
 
 	{"sslnegotiation", "PGSSLNEGOTIATION", DefaultSSLNegotiation, NULL,
-		"SSL-Negotiation", "", 14,	/* sizeof("requiredirect") == 14 */
+		"SSL-Negotiation", "", 9,	/* sizeof("postgres") == 9  */
 	offsetof(struct pg_conn, sslnegotiation)},
 
 	{"sslcompression", "PGSSLCOMPRESSION", "0", NULL,
@@ -1590,8 +1590,7 @@ pqConnectOptions2(PGconn *conn)
 	if (conn->sslnegotiation)
 	{
 		if (strcmp(conn->sslnegotiation, "postgres") != 0
-			&& strcmp(conn->sslnegotiation, "direct") != 0
-			&& strcmp(conn->sslnegotiation, "requiredirect") != 0)
+			&& strcmp(conn->sslnegotiation, "direct") != 0)
 		{
 			conn->status = CONNECTION_BAD;
 			libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
@@ -1608,6 +1607,25 @@ pqConnectOptions2(PGconn *conn)
 			return false;
 		}
 #endif
+
+		/*
+		 * Don't allow direct SSL negotiation with sslmode='prefer', because
+		 * that poses a risk of unintentional fallback to plaintext connection
+		 * when connecting to a pre-v17 server that does not support direct
+		 * SSL connections. To keep things simple, don't allow it with
+		 * sslmode='allow' or sslmode='disable' either. If a user goes through
+		 * the trouble of setting sslnegotiation='direct', they probably
+		 * intend to use SSL, and sslmode=disable or allow is probably a user
+		 * user mistake anyway.
+		 */
+		if (conn->sslnegotiation[0] == 'd' &&
+			conn->sslmode[0] != 'r' && conn->sslmode[0] != 'v')
+		{
+			conn->status = CONNECTION_BAD;
+			libpq_append_conn_error(conn, "weak sslmode \"%s\" may not be used with sslnegotiation=direct (use \"require\", \"verify-ca\", or \"verify-full\")",
+									conn->sslmode);
+			return false;
+		}
 	}
 	else
 	{
@@ -3347,42 +3365,47 @@ keep_going:						/* We will come back to here until there is
 					goto error_return;
 
 				/*
-				 * If direct SSL is enabled, jump right into SSL handshake. We
-				 * will come back here after SSL encryption has been
-				 * established, with ssl_in_use set.
-				 */
-				if (conn->current_enc_method == ENC_DIRECT_SSL && !conn->ssl_in_use)
-				{
-					conn->status = CONNECTION_SSL_STARTUP;
-					return PGRES_POLLING_WRITING;
-				}
-
-				/*
-				 * If negotiated SSL is enabled, request SSL and proceed with
-				 * SSL handshake.  We will come back here after SSL encryption
-				 * has been established, with ssl_in_use set.
+				 * If SSL is enabled, start the SSL negotiation. We will come
+				 * back here after SSL encryption has been established, with
+				 * ssl_in_use set.
 				 */
-				if (conn->current_enc_method == ENC_NEGOTIATED_SSL && !conn->ssl_in_use)
+				if (conn->current_enc_method == ENC_SSL && !conn->ssl_in_use)
 				{
-					ProtocolVersion pv;
-
 					/*
-					 * Send the SSL request packet.
-					 *
-					 * Theoretically, this could block, but it really
-					 * shouldn't since we only got here if the socket is
-					 * write-ready.
+					 * If traditional postgres SSL negotiation is used, send
+					 * the SSL request.  In direct negotiation, jump straight
+					 * into the SSL handshake.
 					 */
-					pv = pg_hton32(NEGOTIATE_SSL_CODE);
-					if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK)
+					if (conn->sslnegotiation[0] == 'p')
 					{
-						libpq_append_conn_error(conn, "could not send SSL negotiation packet: %s",
-												SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
-						goto error_return;
+						ProtocolVersion pv;
+
+						Assert(conn->sslnegotiation[0] == 'p');
+
+						/*
+						 * Send the SSL request packet.
+						 *
+						 * Theoretically, this could block, but it really
+						 * shouldn't since we only got here if the socket is
+						 * write-ready.
+						 */
+						pv = pg_hton32(NEGOTIATE_SSL_CODE);
+						if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK)
+						{
+							libpq_append_conn_error(conn, "could not send SSL negotiation packet: %s",
+													SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+							goto error_return;
+						}
+						/* Ok, wait for response */
+						conn->status = CONNECTION_SSL_STARTUP;
+						return PGRES_POLLING_READING;
+					}
+					else
+					{
+						Assert(conn->sslnegotiation[0] == 'd');
+						conn->status = CONNECTION_SSL_STARTUP;
+						return PGRES_POLLING_WRITING;
 					}
-					/* Ok, wait for response */
-					conn->status = CONNECTION_SSL_STARTUP;
-					return PGRES_POLLING_READING;
 				}
 #endif							/* USE_SSL */
 
@@ -3453,11 +3476,11 @@ keep_going:						/* We will come back to here until there is
 				PostgresPollingStatusType pollres;
 
 				/*
-				 * On first time through, get the postmaster's response to our
-				 * SSL negotiation packet. If we are trying a direct ssl
-				 * connection, go straight to initiating ssl.
+				 * On first time through with traditional SSL negotiation, get
+				 * the postmaster's response to our SSLRequest packet. With
+				 * sslnegotiation='direct', go straight to initiating SSL.
 				 */
-				if (!conn->ssl_in_use && conn->current_enc_method == ENC_NEGOTIATED_SSL)
+				if (!conn->ssl_in_use && conn->sslnegotiation[0] == 'p')
 				{
 					/*
 					 * We use pqReadData here since it has the logic to
@@ -4282,7 +4305,7 @@ init_allowed_encryption_methods(PGconn *conn)
 	if (conn->raddr.addr.ss_family == AF_UNIX)
 	{
 		/* Don't request SSL or GSSAPI over Unix sockets */
-		conn->allowed_enc_methods &= ~(ENC_DIRECT_SSL | ENC_NEGOTIATED_SSL | ENC_GSSAPI);
+		conn->allowed_enc_methods &= ~(ENC_SSL | ENC_GSSAPI);
 
 		/*
 		 * XXX: we probably should not do this. sslmode=require works
@@ -4309,12 +4332,7 @@ init_allowed_encryption_methods(PGconn *conn)
 	/* sslmode anything but 'disable', and GSSAPI not required */
 	if (conn->sslmode[0] != 'd' && conn->gssencmode[0] != 'r')
 	{
-		if (conn->sslnegotiation[0] == 'p')
-			conn->allowed_enc_methods |= ENC_NEGOTIATED_SSL;
-		else if (conn->sslnegotiation[0] == 'd')
-			conn->allowed_enc_methods |= ENC_DIRECT_SSL | ENC_NEGOTIATED_SSL;
-		else if (conn->sslnegotiation[0] == 'r')
-			conn->allowed_enc_methods |= ENC_DIRECT_SSL;
+		conn->allowed_enc_methods |= ENC_SSL;
 	}
 #endif
 
@@ -4354,7 +4372,8 @@ encryption_negotiation_failed(PGconn *conn)
 
 	if (select_next_encryption_method(conn, true))
 	{
-		if (conn->current_enc_method == ENC_DIRECT_SSL)
+		/* An existing connection cannot be reused for direct SSL */
+		if (conn->current_enc_method == ENC_SSL && conn->sslnegotiation[0] == 'd')
 			return 2;
 		else
 			return 1;
@@ -4376,18 +4395,6 @@ connection_failed(PGconn *conn)
 	Assert((conn->failed_enc_methods & conn->current_enc_method) == 0);
 	conn->failed_enc_methods |= conn->current_enc_method;
 
-	/*
-	 * If the server reported an error after the SSL handshake, no point in
-	 * retrying with negotiated vs direct SSL.
-	 */
-	if ((conn->current_enc_method & (ENC_DIRECT_SSL | ENC_NEGOTIATED_SSL)) != 0 &&
-		conn->ssl_handshake_started)
-	{
-		conn->failed_enc_methods |= (ENC_DIRECT_SSL | ENC_NEGOTIATED_SSL) & conn->allowed_enc_methods;
-	}
-	else
-		conn->failed_enc_methods |= conn->current_enc_method;
-
 	return select_next_encryption_method(conn, false);
 }
 
@@ -4445,24 +4452,17 @@ select_next_encryption_method(PGconn *conn, bool have_valid_connection)
 	SELECT_NEXT_METHOD(ENC_GSSAPI);
 #endif
 
-	/* With sslmode=allow, try plaintext connection before SSL. */
-	if (conn->sslmode[0] == 'a')
-		SELECT_NEXT_METHOD(ENC_PLAINTEXT);
-
 	/*
-	 * If enabled, try direct SSL. Unless we have a valid TCP connection that
-	 * failed negotiating GSSAPI encryption; in that case we prefer to reuse
-	 * the connection with negotiated SSL, instead of reconnecting to do
-	 * direct SSL. The point of sslnegotiation=direct is to avoid the
-	 * roundtrip from the negotiation, but reconnecting would also incur a
-	 * roundtrip. (In sslnegotiation=requiredirect mode, negotiated SSL is not
-	 * in the list of allowed methods and we will reconnect.)
+	 * The order between SSL encryption and plaintext depends on sslmode. With
+	 * sslmode=allow, try plaintext connection before SSL. With
+	 * sslmode=prefer, it's the other way round. With other modes, we only try
+	 * plaintext or SSL connections so the order they're listed here doesn't
+	 * matter.
 	 */
-	if (have_valid_connection)
-		SELECT_NEXT_METHOD(ENC_NEGOTIATED_SSL);
+	if (conn->sslmode[0] == 'a')
+		SELECT_NEXT_METHOD(ENC_PLAINTEXT);
 
-	SELECT_NEXT_METHOD(ENC_DIRECT_SSL);
-	SELECT_NEXT_METHOD(ENC_NEGOTIATED_SSL);
+	SELECT_NEXT_METHOD(ENC_SSL);
 
 	if (conn->sslmode[0] != 'a')
 		SELECT_NEXT_METHOD(ENC_PLAINTEXT);
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index fb6bb911f5..bf9dfbf918 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -1586,7 +1586,7 @@ open_client_SSL(PGconn *conn)
 	}
 
 	/* ALPN is mandatory with direct SSL connections */
-	if (conn->current_enc_method == ENC_DIRECT_SSL)
+	if (conn->current_enc_method == ENC_SSL && conn->sslnegotiation[0] == 'd')
 	{
 		const unsigned char *selected;
 		unsigned int len;
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 3691e5ee96..3886204c70 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -235,8 +235,7 @@ typedef enum
 #define ENC_ERROR			0
 #define ENC_PLAINTEXT		0x01
 #define ENC_GSSAPI			0x02
-#define ENC_DIRECT_SSL		0x04
-#define ENC_NEGOTIATED_SSL	0x08
+#define ENC_SSL				0x04
 
 /* Target server type (decoded value of target_session_attrs) */
 typedef enum
@@ -395,8 +394,7 @@ struct pg_conn
 	char	   *keepalives_count;	/* maximum number of TCP keepalive
 									 * retransmits */
 	char	   *sslmode;		/* SSL mode (require,prefer,allow,disable) */
-	char	   *sslnegotiation; /* SSL initiation style
-								 * (postgres,direct,requiredirect) */
+	char	   *sslnegotiation; /* SSL initiation style (postgres,direct) */
 	char	   *sslcompression; /* SSL compression (0 or 1) */
 	char	   *sslkey;			/* client key filename */
 	char	   *sslcert;		/* client certificate filename */
diff --git a/src/interfaces/libpq/t/005_negotiate_encryption.pl b/src/interfaces/libpq/t/005_negotiate_encryption.pl
index bc19cd244b..c3f70d31bc 100644
--- a/src/interfaces/libpq/t/005_negotiate_encryption.pl
+++ b/src/interfaces/libpq/t/005_negotiate_encryption.pl
@@ -213,7 +213,7 @@ my @all_test_users =
   ('testuser', 'ssluser', 'nossluser', 'gssuser', 'nogssuser');
 my @all_gssencmodes = ('disable', 'prefer', 'require');
 my @all_sslmodes = ('disable', 'allow', 'prefer', 'require');
-my @all_sslnegotiations = ('postgres', 'direct', 'requiredirect');
+my @all_sslnegotiations = ('postgres', 'direct');
 
 my $server_config = {
 	server_ssl => 0,
@@ -228,23 +228,22 @@ if ($ssl_supported)
 {
 	$test_table = q{
 # USER      GSSENCMODE   SSLMODE      SSLNEGOTIATION EVENTS                      -> OUTCOME
-testuser    disable      disable      *              connect, authok             -> plain
-.           .            allow        *              connect, authok             -> plain
+testuser    disable      disable      postgres       connect, authok             -> plain
+.           .            allow        postgres       connect, authok             -> plain
 .           .            prefer       postgres       connect, sslreject, authok  -> plain
-.           .            .            direct         connect, directsslreject, reconnect, sslreject, authok  -> plain
-.           .            .            requiredirect  connect, directsslreject, reconnect, authok -> plain
 .           .            require      postgres       connect, sslreject          -> fail
-.           .            .            direct         connect, directsslreject, reconnect, sslreject -> fail
-.           .            .            requiredirect  connect, directsslreject    -> fail
-.           prefer       disable      *              connect, authok             -> plain
-.           .            allow        *              connect, authok             -> plain
+.           .            .            direct         connect, directsslreject    -> fail
+.           prefer       disable      postgres       connect, authok             -> plain
+.           .            allow        postgres       connect, authok             -> plain
 .           .            prefer       postgres       connect, sslreject, authok  -> plain
-.           .            .            direct         connect, directsslreject, reconnect, sslreject, authok  -> plain
-.           .            .            requiredirect  connect, directsslreject, reconnect, authok -> plain
 .           .            require      postgres       connect, sslreject          -> fail
-.           .            .            direct         connect, directsslreject, reconnect, sslreject -> fail
-.           .            .            requiredirect  connect, directsslreject    -> fail
-	};
+.           .            .            direct         connect, directsslreject    -> fail
+
+# sslnegotiation=direct is not acccepted unless sslmode=require or stronger
+*           *            disable      direct         -     -> fail
+*           *            allow        direct         -     -> fail
+*           *            prefer       direct         -     -> fail
+};
 }
 else
 {
@@ -258,11 +257,10 @@ testuser    disable      disable      postgres       connect, authok
 .           .            allow        postgres       connect, authok             -> plain
 .           .            prefer       postgres       connect, authok             -> plain
 
-# Without SSL support, sslmode=require and sslnegotiation=direct/requiredirect
-# are not accepted at all.
-.           *            require      *              -     -> fail
-.           *            *            direct         -     -> fail
-.           *            *            requiredirect  -     -> fail
+# Without SSL support, sslmode=require and sslnegotiation=direct are
+# not accepted at all
+*           *            require      *              -     -> fail
+*           *            *            direct         -     -> fail
 	};
 }
 
@@ -288,34 +286,26 @@ SKIP:
 
 	$test_table = q{
 # USER      GSSENCMODE   SSLMODE      SSLNEGOTIATION EVENTS                                          -> OUTCOME
-testuser    disable      disable      *              connect, authok                                 -> plain
-.           .            allow        *              connect, authok                                 -> plain
+testuser    disable      disable      postgres       connect, authok                                 -> plain
+.           .            allow        postgres       connect, authok                                 -> plain
 .           .            prefer       postgres       connect, sslaccept, authok                      -> ssl
-.           .            .            direct         connect, directsslaccept, authok                -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok                -> ssl
 .           .            require      postgres       connect, sslaccept, authok                      -> ssl
 .           .            .            direct         connect, directsslaccept, authok                -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok                -> ssl
-ssluser     .            disable      *              connect, authfail                               -> fail
+ssluser     .            disable      postgres       connect, authfail                               -> fail
 .           .            allow        postgres       connect, authfail, reconnect, sslaccept, authok -> ssl
-.           .            .            direct         connect, authfail, reconnect, directsslaccept, authok -> ssl
-.           .            .            requiredirect  connect, authfail, reconnect, directsslaccept, authok -> ssl
 .           .            prefer       postgres       connect, sslaccept, authok                      -> ssl
-.           .            .            direct         connect, directsslaccept, authok                -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok                -> ssl
 .           .            require      postgres       connect, sslaccept, authok                      -> ssl
 .           .            .            direct         connect, directsslaccept, authok                -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok                -> ssl
-nossluser   .            disable      *              connect, authok                                 -> plain
+nossluser   .            disable      postgres       connect, authok                                 -> plain
 .           .            allow        postgres       connect, authok                                 -> plain
-.           .            .            direct         connect, authok                                 -> plain
-.           .            .            requiredirect  connect, authok                                 -> plain
 .           .            prefer       postgres       connect, sslaccept, authfail, reconnect, authok -> plain
-.           .            .            direct         connect, directsslaccept, authfail, reconnect, authok -> plain
-.           .            .            requiredirect  connect, directsslaccept, authfail, reconnect, authok -> plain
 .           .            require      postgres       connect, sslaccept, authfail                    -> fail
 .           .            require      direct         connect, directsslaccept, authfail              -> fail
-.           .            require      requiredirect  connect, directsslaccept, authfail              -> fail
+
+# sslnegotiation=direct is not acccepted unless sslmode=require or stronger
+*           *            disable      direct         -     -> fail
+*           *            allow        direct         -     -> fail
+*           *            prefer       direct         -     -> fail
 };
 
 	# Enable SSL in the server
@@ -350,62 +340,54 @@ SKIP:
 
 	$test_table = q{
 # USER      GSSENCMODE   SSLMODE      SSLNEGOTIATION EVENTS                       -> OUTCOME
-testuser    disable      disable      *              connect, authok              -> plain
-.           .            allow        *              connect, authok              -> plain
+testuser    disable      disable      postgres       connect, authok              -> plain
+.           .            allow        postgres       connect, authok              -> plain
 .           .            prefer       postgres       connect, sslreject, authok   -> plain
-.           .            .            direct         connect, directsslreject, reconnect, sslreject, authok  -> plain
-.           .            .            requiredirect  connect, directsslreject, reconnect, authok             -> plain
 .           .            require      postgres       connect, sslreject                -> fail
-.           .            .            direct         connect, directsslreject, reconnect, sslreject -> fail
-.           .            .            requiredirect  connect, directsslreject          -> fail
-.           prefer       *            *              connect, gssaccept, authok        -> gss
-.           require      *            *              connect, gssaccept, authok        -> gss
+.           .            .            direct         connect, directsslreject          -> fail
+.           prefer       *            postgres       connect, gssaccept, authok        -> gss
+.           prefer       require      direct         connect, gssaccept, authok        -> gss
+.           require      *            postgres       connect, gssaccept, authok        -> gss
+.           .            require      direct         connect, gssaccept, authok        -> gss
 
-gssuser     disable      disable      *              connect, authfail                  -> fail
+gssuser     disable      disable      postgres       connect, authfail                  -> fail
 .           .            allow        postgres       connect, authfail, reconnect, sslreject -> fail
-.           .            .            direct         connect, authfail, reconnect, directsslreject, reconnect, sslreject -> fail
-.           .            .            requiredirect  connect, authfail, reconnect, directsslreject -> fail
 .           .            prefer       postgres       connect, sslreject, authfail       -> fail
-.           .            .            direct         connect, directsslreject, reconnect, sslreject, authfail -> fail
-.           .            .            requiredirect  connect, directsslreject, reconnect, authfail -> fail
 .           .            require      postgres       connect, sslreject                 -> fail
-.           .            .            direct         connect, directsslreject, reconnect, sslreject -> fail
-.           .            .            requiredirect  connect, directsslreject           -> fail
-.           prefer       *            *              connect, gssaccept, authok   -> gss
-.           require      *            *              connect, gssaccept, authok   -> gss
+.           .            .            direct         connect, directsslreject           -> fail
+.           prefer       *            postgres       connect, gssaccept, authok   -> gss
+.           prefer       require      direct         connect, gssaccept, authok   -> gss
+.           require      *            postgres       connect, gssaccept, authok   -> gss
+.           .            require      direct         connect, gssaccept, authok   -> gss
 
-nogssuser   disable      disable      *              connect, authok              -> plain
+nogssuser   disable      disable      postgres       connect, authok              -> plain
 .           .            allow        postgres       connect, authok              -> plain
-.           .            .            direct         connect, authok              -> plain
-.           .            .            requiredirect  connect, authok              -> plain
 .           .            prefer       postgres       connect, sslreject, authok   -> plain
-.           .            .            direct         connect, directsslreject, reconnect, sslreject, authok   -> plain
-.           .            .            requiredirect  connect, directsslreject, reconnect, authok              -> plain
 .           .            require      postgres       connect, sslreject                 -> fail
-.           .            .            direct         connect, directsslreject, reconnect, sslreject -> fail
-.           .            .            requiredirect  connect, directsslreject           -> fail
-.           prefer       disable      *              connect, gssaccept, authfail, reconnect, authok             -> plain
+.           .            .            direct         connect, directsslreject           -> fail
+.           prefer       disable      postgres       connect, gssaccept, authfail, reconnect, authok             -> plain
 .           .            allow        postgres       connect, gssaccept, authfail, reconnect, authok             -> plain
-.           .            .            direct         connect, gssaccept, authfail, reconnect, authok             -> plain
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, authok             -> plain
 .           .            prefer       postgres       connect, gssaccept, authfail, reconnect, sslreject, authok  -> plain
-.           .            .            direct         connect, gssaccept, authfail, reconnect, directsslreject, reconnect, sslreject, authok  -> plain
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, directsslreject, reconnect, authok  -> plain
 .           .            require      postgres       connect, gssaccept, authfail, reconnect, sslreject          -> fail
-.           .            .            direct         connect, gssaccept, authfail, reconnect, directsslreject, reconnect, sslreject          -> fail
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, directsslreject          -> fail
-.           require      disable      *              connect, gssaccept, authfail -> fail
-.           .            allow        *              connect, gssaccept, authfail -> fail
-.           .            prefer       *              connect, gssaccept, authfail -> fail
-.           .            require      *              connect, gssaccept, authfail -> fail   # If both GSSAPI and sslmode are required, and GSS is not available -> fail
+.           .            .            direct         connect, gssaccept, authfail, reconnect, directsslreject          -> fail
+.           require      disable      postgres       connect, gssaccept, authfail -> fail
+.           .            allow        postgres       connect, gssaccept, authfail -> fail
+.           .            prefer       postgres       connect, gssaccept, authfail -> fail
+.           .            require      postgres       connect, gssaccept, authfail -> fail   # If both GSSAPI and sslmode are required, and GSS is not available -> fail
+.           .            .            direct         connect, gssaccept, authfail -> fail   # If both GSSAPI and sslmode are required, and GSS is not available -> fail
+
+# sslnegotiation=direct is not acccepted unless sslmode=require or stronger
+*           *            disable      direct         -     -> fail
+*           *            allow        direct         -     -> fail
+*           *            prefer       direct         -     -> fail
 	};
 
 	# The expected events and outcomes above assume that SSL support
 	# is enabled. When libpq is compiled without SSL support, all
 	# attempts to connect with sslmode=require or
-	# sslnegotiation=direct/requiredirect would fail immediately without
-	# even connecting to the server. Skip those, because we tested
-	# them earlier already.
+	# sslnegotiation=direct would fail immediately without even
+	# connecting to the server. Skip those, because we tested them
+	# earlier already.
 	my ($sslmodes, $sslnegotiations);
 	if ($ssl_supported != 0)
 	{
@@ -445,100 +427,84 @@ SKIP:
 
 	$test_table = q{
 # USER      GSSENCMODE   SSLMODE      SSLNEGOTIATION EVENTS                       -> OUTCOME
-testuser    disable      disable      *              connect, authok              -> plain
-.           .            allow        *              connect, authok              -> plain
-.           .            prefer       postgres       connect, sslaccept, authok         -> ssl
-.           .            .            direct         connect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok   -> ssl
-.           .            require      postgres       connect, sslaccept, authok         -> ssl
+testuser    disable      disable      postgres       connect, authok              -> plain
+.           .            allow        postgres       connect, authok              -> plain
+.           .            prefer       postgres       connect, sslaccept, authok   -> ssl
+.           .            require      postgres       connect, sslaccept, authok   -> ssl
 .           .            .            direct         connect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok   -> ssl
-.           prefer       disable      *              connect, gssaccept, authok   -> gss
-.           .            allow        *              connect, gssaccept, authok   -> gss
-.           .            prefer       *              connect, gssaccept, authok   -> gss
-.           .            require      *              connect, gssaccept, authok   -> gss     # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require
-.           require      disable      *              connect, gssaccept, authok   -> gss
-.           .            allow        *              connect, gssaccept, authok   -> gss
-.           .            prefer       *              connect, gssaccept, authok   -> gss
-.           .            require      *              connect, gssaccept, authok   -> gss     # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require
-
-gssuser     disable      disable      *              connect, authfail            -> fail
+.           prefer       disable      postgres       connect, gssaccept, authok   -> gss
+.           .            allow        postgres       connect, gssaccept, authok   -> gss
+.           .            prefer       postgres       connect, gssaccept, authok   -> gss
+.           .            require      postgres       connect, gssaccept, authok   -> gss     # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require
+.           .            .            direct         connect, gssaccept, authok   -> gss
+.           require      disable      postgres       connect, gssaccept, authok   -> gss
+.           .            allow        postgres       connect, gssaccept, authok   -> gss
+.           .            prefer       postgres       connect, gssaccept, authok   -> gss
+.           .            require      postgres       connect, gssaccept, authok   -> gss     # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require
+.           .            .            direct         connect, gssaccept, authok   -> gss
+
+gssuser     disable      disable      postgres       connect, authfail            -> fail
 .           .            allow        postgres       connect, authfail, reconnect, sslaccept, authfail -> fail
-.           .            .            direct         connect, authfail, reconnect, directsslaccept, authfail -> fail
-.           .            .            requiredirect  connect, authfail, reconnect, directsslaccept, authfail -> fail
 .           .            prefer       postgres       connect, sslaccept, authfail, reconnect, authfail -> fail
-.           .            .            direct         connect, directsslaccept, authfail, reconnect, authfail -> fail
-.           .            .            requiredirect  connect, directsslaccept, authfail, reconnect, authfail -> fail
 .           .            require      postgres       connect, sslaccept, authfail       -> fail
 .           .            .            direct         connect, directsslaccept, authfail -> fail
-.           .            .            requiredirect  connect, directsslaccept, authfail -> fail
-.           prefer       disable      *              connect, gssaccept, authok   -> gss
-.           .            allow        *              connect, gssaccept, authok   -> gss
-.           .            prefer       *              connect, gssaccept, authok   -> gss
-.           .            require      *              connect, gssaccept, authok   -> gss   # GSS is chosen over SSL, even though sslmode=require
-.           require      disable      *              connect, gssaccept, authok   -> gss
-.           .            allow        *              connect, gssaccept, authok   -> gss
-.           .            prefer       *              connect, gssaccept, authok   -> gss
-.           .            require      *              connect, gssaccept, authok   -> gss     # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require
-
-ssluser     disable      disable      *              connect, authfail            -> fail
+.           prefer       disable      postgres       connect, gssaccept, authok   -> gss
+.           .            allow        postgres       connect, gssaccept, authok   -> gss
+.           .            prefer       postgres       connect, gssaccept, authok   -> gss
+.           .            require      postgres       connect, gssaccept, authok   -> gss   # GSS is chosen over SSL, even though sslmode=require
+.           .            .            direct         connect, gssaccept, authok   -> gss
+.           require      disable      postgres       connect, gssaccept, authok   -> gss
+.           .            allow        postgres       connect, gssaccept, authok   -> gss
+.           .            prefer       postgres       connect, gssaccept, authok   -> gss
+.           .            require      postgres       connect, gssaccept, authok   -> gss     # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require
+.           .            .            direct         connect, gssaccept, authok   -> gss
+
+ssluser     disable      disable      postgres       connect, authfail            -> fail
 .           .            allow        postgres       connect, authfail, reconnect, sslaccept, authok       -> ssl
-.           .            .            direct         connect, authfail, reconnect, directsslaccept, authok -> ssl
-.           .            .            requiredirect  connect, authfail, reconnect, directsslaccept, authok -> ssl
 .           .            prefer       postgres       connect, sslaccept, authok         -> ssl
-.           .            .            direct         connect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok   -> ssl
 .           .            require      postgres       connect, sslaccept, authok         -> ssl
 .           .            .            direct         connect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok   -> ssl
-.           prefer       disable      *              connect, gssaccept, authfail, reconnect, authfail -> fail
+.           prefer       disable      postgres       connect, gssaccept, authfail, reconnect, authfail -> fail
 .           .            allow        postgres       connect, gssaccept, authfail, reconnect, authfail, reconnect, sslaccept, authok       -> ssl
-.           .            .            direct         connect, gssaccept, authfail, reconnect, authfail, reconnect, directsslaccept, authok -> ssl
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, authfail, reconnect, directsslaccept, authok -> ssl
 .           .            prefer       postgres       connect, gssaccept, authfail, reconnect, sslaccept, authok       -> ssl
-.           .            .            direct         connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl
 .           .            require      postgres       connect, gssaccept, authfail, reconnect, sslaccept, authok       -> ssl
 .           .            .            direct         connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl
-.           require      disable      *              connect, gssaccept, authfail -> fail
-.           .            allow        *              connect, gssaccept, authfail -> fail
-.           .            prefer       *              connect, gssaccept, authfail -> fail
-.           .            require      *              connect, gssaccept, authfail -> fail         # If both GSS and SSL are required, the sslmode=require is effectively ignored and GSS is required
+.           require      disable      postgres       connect, gssaccept, authfail -> fail
+.           .            allow        postgres       connect, gssaccept, authfail -> fail
+.           .            prefer       postgres       connect, gssaccept, authfail -> fail
+.           .            require      postgres       connect, gssaccept, authfail -> fail         # If both GSS and SSL are required, the sslmode=require is effectively ignored and GSS is required
+.           .            .            direct         connect, gssaccept, authfail -> fail
 
-nogssuser   disable      disable      *              connect, authok              -> plain
+nogssuser   disable      disable      postgres       connect, authok              -> plain
 .           .            allow        postgres       connect, authok              -> plain
-.           .            .            direct         connect, authok              -> plain
-.           .            .            requiredirect  connect, authok              -> plain
 .           .            prefer       postgres       connect, sslaccept, authok   -> ssl
-.           .            .            direct         connect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok   -> ssl
 .           .            require      postgres       connect, sslaccept, authok   -> ssl
 .           .            .            direct         connect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, directsslaccept, authok   -> ssl
-.           prefer       disable      *              connect, gssaccept, authfail, reconnect, authok              -> plain
-.           .            allow        *              connect, gssaccept, authfail, reconnect, authok              -> plain
+.           prefer       disable      postgres       connect, gssaccept, authfail, reconnect, authok              -> plain
+.           .            allow        postgres       connect, gssaccept, authfail, reconnect, authok              -> plain
 .           .            prefer       postgres       connect, gssaccept, authfail, reconnect, sslaccept, authok         -> ssl
-.           .            .            direct         connect, gssaccept, authfail, reconnect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, directsslaccept, authok   -> ssl
 .           .            require      postgres       connect, gssaccept, authfail, reconnect, sslaccept, authok         -> ssl
 .           .            .            direct         connect, gssaccept, authfail, reconnect, directsslaccept, authok   -> ssl
-.           .            .            requiredirect  connect, gssaccept, authfail, reconnect, directsslaccept, authok   -> ssl
-.           require      disable      *              connect, gssaccept, authfail -> fail
-.           .            allow        *              connect, gssaccept, authfail -> fail
-.           .            prefer       *              connect, gssaccept, authfail -> fail
-.           .            require      *              connect, gssaccept, authfail -> fail   # If both GSS and SSL are required, the sslmode=require is effectively ignored and GSS is required
-
-nossluser   disable      disable      *              connect, authok              -> plain
-.           .            allow        *              connect, authok              -> plain
+.           require      disable      postgres       connect, gssaccept, authfail -> fail
+.           .            allow        postgres       connect, gssaccept, authfail -> fail
+.           .            prefer       postgres       connect, gssaccept, authfail -> fail
+.           .            require      postgres       connect, gssaccept, authfail -> fail   # If both GSS and SSL are required, the sslmode=require is effectively ignored and GSS is required
+.           .            .            direct         connect, gssaccept, authfail -> fail
+
+nossluser   disable      disable      postgres       connect, authok              -> plain
+.           .            allow        postgres       connect, authok              -> plain
 .           .            prefer       postgres       connect, sslaccept, authfail, reconnect, authok       -> plain
-.           .            .            direct         connect, directsslaccept, authfail, reconnect, authok -> plain
-.           .            .            requiredirect  connect, directsslaccept, authfail, reconnect, authok -> plain
 .           .            require      postgres       connect, sslaccept, authfail       -> fail
 .           .            .            direct         connect, directsslaccept, authfail -> fail
-.           .            .            requiredirect  connect, directsslaccept, authfail -> fail
-.           prefer       *            *              connect, gssaccept, authok   -> gss
-.           require      *            *              connect, gssaccept, authok   -> gss
+.           prefer       *            postgres       connect, gssaccept, authok   -> gss
+.           .            require      direct         connect, gssaccept, authok   -> gss
+.           require      *            postgres       connect, gssaccept, authok   -> gss
+.           .            require      direct         connect, gssaccept, authok   -> gss
+
+# sslnegotiation=direct is not acccepted unless sslmode=require or stronger
+*           *            disable      direct         -     -> fail
+*           *            allow        direct         -     -> fail
+*           *            prefer       direct         -     -> fail
 	};
 
 	note("Running tests with both GSS and SSL enabled in server");
-- 
2.39.2

#55Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Heikki Linnakangas (#54)
Re: Direct SSL connection with ALPN and HBA rules

On Wed, May 15, 2024 at 6:33 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Ok, yeah, I can see that now. Here's a new version to address that. I
merged ENC_SSL_NEGOTIATED_SSL and ENC_SSL_DIRECT_SSL to a single method,
ENC_SSL. The places that need to distinguish between them now check
conn-sslnegotiation. That seems more clear now that there is no fallback.

That change and the new comment that were added seem a lot clearer to
me, too; +1. And I like that this potentially preps for
encryption=gss/ssl/none or similar.

This assertion seems a little strange to me:

if (conn->sslnegotiation[0] == 'p')
{
ProtocolVersion pv;

Assert(conn->sslnegotiation[0] == 'p');

But other than that nitpick, nothing else jumps out at me at the moment.

Thanks,
--Jacob

#56Robert Haas
robertmhaas@gmail.com
In reply to: Heikki Linnakangas (#54)
Re: Direct SSL connection with ALPN and HBA rules

On Wed, May 15, 2024 at 9:33 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Ok, yeah, I can see that now. Here's a new version to address that. I
merged ENC_SSL_NEGOTIATED_SSL and ENC_SSL_DIRECT_SSL to a single method,
ENC_SSL. The places that need to distinguish between them now check
conn-sslnegotiation. That seems more clear now that there is no fallback.

Unless there is a compelling reason to do otherwise, we should
expedite getting this committed so that it is included in beta1.
Release freeze begins Saturday.

Thanks,

--
Robert Haas
EDB: http://www.enterprisedb.com

#57Daniel Gustafsson
daniel@yesql.se
In reply to: Robert Haas (#56)
Re: Direct SSL connection with ALPN and HBA rules

On 16 May 2024, at 15:54, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, May 15, 2024 at 9:33 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Ok, yeah, I can see that now. Here's a new version to address that. I
merged ENC_SSL_NEGOTIATED_SSL and ENC_SSL_DIRECT_SSL to a single method,
ENC_SSL. The places that need to distinguish between them now check
conn-sslnegotiation. That seems more clear now that there is no fallback.

Unless there is a compelling reason to do otherwise, we should
expedite getting this committed so that it is included in beta1.
Release freeze begins Saturday.

+1. Having reread the thread and patch I think we should go for this one.

./daniel

#58Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Daniel Gustafsson (#57)
Re: Direct SSL connection with ALPN and HBA rules

On 16/05/2024 17:08, Daniel Gustafsson wrote:

On 16 May 2024, at 15:54, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, May 15, 2024 at 9:33 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Ok, yeah, I can see that now. Here's a new version to address that. I
merged ENC_SSL_NEGOTIATED_SSL and ENC_SSL_DIRECT_SSL to a single method,
ENC_SSL. The places that need to distinguish between them now check
conn-sslnegotiation. That seems more clear now that there is no fallback.

Unless there is a compelling reason to do otherwise, we should
expedite getting this committed so that it is included in beta1.
Release freeze begins Saturday.

+1. Having reread the thread and patch I think we should go for this one.

Yep, committed. Thanks everyone!

On 15/05/2024 21:24, Jacob Champion wrote:

This assertion seems a little strange to me:

if (conn->sslnegotiation[0] == 'p')
{
ProtocolVersion pv;

Assert(conn->sslnegotiation[0] == 'p');

But other than that nitpick, nothing else jumps out at me at the moment.

Fixed that. It was a leftover, I had the if-else conditions the other
way round at one point during development.

--
Heikki Linnakangas
Neon (https://neon.tech)

#59Robert Haas
robertmhaas@gmail.com
In reply to: Heikki Linnakangas (#58)
Re: Direct SSL connection with ALPN and HBA rules

On Thu, May 16, 2024 at 10:23 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Yep, committed. Thanks everyone!

Thanks!

--
Robert Haas
EDB: http://www.enterprisedb.com