Skip to content

Commit 865a88f

Browse files
authored
Add support for direct SSL negotiation (PostgreSQL 17+) (#1659)
* Add support for direct SSL negotiation (PostgreSQL 17+) Closes #1536 - Added SslNegotiation enum with POSTGRES (default) and DIRECT modes - Extended PgConnectOptions with sslNegotiation field, supporting configuration via API, connection URI, and environment variable - Refactored PgConnectionFactory to handle direct SSL with ALPN protocol negotiation - Added tests with PostgreSQL 17+ version checks - Updated documentation The implementation maintains full backward compatibility by defaulting to traditional negotiation (POSTGRES mode). Some portions of this content were created with the assistance of Claude Code. Signed-off-by: Thomas Segismont <tsegismont@gmail.com> * Safely copy ClientSSLOptions In case it's null, create a new instance Signed-off-by: Thomas Segismont <tsegismont@gmail.com> --------- Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
1 parent cd730ed commit 865a88f

File tree

10 files changed

+365
-103
lines changed

10 files changed

+365
-103
lines changed

vertx-pg-client/src/main/asciidoc/index.adoc

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ Currently, the client supports the following parameter keys:
194194
* `password`
195195
* `dbname`
196196
* `sslmode`
197+
* `sslnegotiation`
197198

198199
Additional parameters will be added to the {@link io.vertx.sqlclient.SqlConnectOptions#getProperties properties}.
199200

@@ -212,6 +213,7 @@ for more details. The following parameters are supported:
212213
* `PGUSER`
213214
* `PGPASSWORD`
214215
* `PGSSLMODE`
216+
* `PGSSLNEGOTIATION`
215217

216218
If you don't specify a data object or a connection URI string to connect, environment variables will take precedence over them.
217219

@@ -731,6 +733,38 @@ All https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-PROTECTION[
731733
{@link examples.PgClientExamples#ex10}
732734
----
733735

736+
=== SSL Negotiation (PostgreSQL 17+)
737+
738+
PostgreSQL 17 introduced direct SSL negotiation, which allows the client to establish TLS immediately without the traditional protocol handshake.
739+
This reduces connection latency by one round-trip and enables standard SSL proxies (nginx, haproxy) to terminate TLS for PostgreSQL connections.
740+
741+
The client supports two SSL negotiation modes via {@link io.vertx.pgclient.SslNegotiation}:
742+
743+
- `POSTGRES` (default): Traditional negotiation where the client sends an SSL request and waits for the server's response before establishing TLS
744+
- `DIRECT`: Direct negotiation where TLS is established immediately (requires PostgreSQL 17+)
745+
746+
[source,$lang]
747+
----
748+
{@link examples.PgClientExamples#sslNegotiationDirect}
749+
----
750+
751+
You can also configure SSL negotiation via connection URI:
752+
753+
[source,$lang]
754+
----
755+
{@link examples.PgClientExamples#sslNegotiationUri}
756+
----
757+
758+
Or using the environment variable:
759+
760+
[source,bash]
761+
----
762+
export PGSSLNEGOTIATION=direct
763+
----
764+
765+
NOTE: Direct SSL negotiation requires PostgreSQL 17 or later.
766+
Attempting to use `DIRECT` mode with older PostgreSQL versions will result in an SSL handshake failure.
767+
734768
More information can be found in the http://vertx.io/docs/vertx-core/java/#ssl[Vert.x documentation].
735769

736770
== Using a level 4 proxy

vertx-pg-client/src/main/java/examples/PgClientExamples.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@
1717
import io.vertx.core.net.ClientSSLOptions;
1818
import io.vertx.core.net.PemTrustOptions;
1919
import io.vertx.docgen.Source;
20-
import io.vertx.pgclient.PgBuilder;
21-
import io.vertx.pgclient.PgConnectOptions;
22-
import io.vertx.pgclient.PgConnection;
23-
import io.vertx.pgclient.SslMode;
20+
import io.vertx.pgclient.*;
2421
import io.vertx.pgclient.pubsub.PgSubscriber;
2522
import io.vertx.sqlclient.*;
2623
import io.vertx.sqlclient.data.Numeric;
@@ -515,6 +512,33 @@ public void ex10(Vertx vertx) {
515512
});
516513
}
517514

515+
public void sslNegotiationDirect(Vertx vertx) {
516+
PgConnectOptions options = new PgConnectOptions()
517+
.setPort(5432)
518+
.setHost("the-host")
519+
.setDatabase("the-db")
520+
.setUser("user")
521+
.setPassword("secret")
522+
.setSslMode(SslMode.REQUIRE)
523+
.setSslNegotiation(SslNegotiation.DIRECT)
524+
.setSslOptions(new ClientSSLOptions()
525+
.setTrustOptions(new PemTrustOptions().addCertPath("/path/to/server.crt")));
526+
527+
PgConnection.connect(vertx, options)
528+
.onComplete(res -> {
529+
if (res.succeeded()) {
530+
System.out.println("Connected with direct SSL negotiation");
531+
} else {
532+
System.out.println("Could not connect: " + res.cause().getMessage());
533+
}
534+
});
535+
}
536+
537+
public void sslNegotiationUri() {
538+
String connectionUri = "postgresql://localhost/mydb?sslmode=require&sslnegotiation=direct";
539+
PgConnectOptions options = PgConnectOptions.fromUri(connectionUri);
540+
}
541+
518542
public void jsonExample() {
519543

520544
// Create a tuple

vertx-pg-client/src/main/java/io/vertx/pgclient/PgConnectOptions.java

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
/*
2-
* Copyright (C) 2017 Julien Viet
2+
* Copyright (c) 2011-2026 Contributors to the Eclipse Foundation
33
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7+
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
158
*
9+
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
1610
*/
1711

1812
package io.vertx.pgclient;
@@ -102,6 +96,9 @@ public static PgConnectOptions fromEnv() {
10296
if (getenv("PGSSLMODE") != null) {
10397
pgConnectOptions.setSslMode(SslMode.of(getenv("PGSSLMODE")));
10498
}
99+
if (getenv("PGSSLNEGOTIATION") != null) {
100+
pgConnectOptions.setSslNegotiation(SslNegotiation.of(getenv("PGSSLNEGOTIATION")));
101+
}
105102
return pgConnectOptions;
106103
}
107104

@@ -112,6 +109,7 @@ public static PgConnectOptions fromEnv() {
112109
public static final String DEFAULT_PASSWORD = "pass";
113110
public static final int DEFAULT_PIPELINING_LIMIT = 256;
114111
public static final SslMode DEFAULT_SSLMODE = SslMode.DISABLE;
112+
public static final SslNegotiation DEFAULT_SSL_NEGOTIATION = SslNegotiation.POSTGRES;
115113
public static final boolean DEFAULT_USE_LAYER_7_PROXY = false;
116114
public static final Map<String, String> DEFAULT_PROPERTIES;
117115

@@ -126,6 +124,7 @@ public static PgConnectOptions fromEnv() {
126124

127125
private int pipeliningLimit = DEFAULT_PIPELINING_LIMIT;
128126
private SslMode sslMode = DEFAULT_SSLMODE;
127+
private SslNegotiation sslNegotiation = DEFAULT_SSL_NEGOTIATION;
129128
private boolean useLayer7Proxy = DEFAULT_USE_LAYER_7_PROXY;
130129

131130
public PgConnectOptions() {
@@ -143,13 +142,15 @@ public PgConnectOptions(SqlConnectOptions other) {
143142
PgConnectOptions opts = (PgConnectOptions) other;
144143
pipeliningLimit = opts.pipeliningLimit;
145144
sslMode = opts.sslMode;
145+
sslNegotiation = opts.sslNegotiation;
146146
}
147147
}
148148

149149
public PgConnectOptions(PgConnectOptions other) {
150150
super(other);
151151
pipeliningLimit = other.pipeliningLimit;
152152
sslMode = other.sslMode;
153+
sslNegotiation = other.sslNegotiation;
153154
}
154155

155156
@Override
@@ -239,6 +240,24 @@ public PgConnectOptions setSslMode(SslMode sslmode) {
239240
return this;
240241
}
241242

243+
/**
244+
* @return the value of current SSL negotiation mode
245+
*/
246+
public SslNegotiation getSslNegotiation() {
247+
return sslNegotiation;
248+
}
249+
250+
/**
251+
* Set {@link SslNegotiation} for the client, this option controls how SSL/TLS is negotiated with the server.
252+
*
253+
* @param sslNegotiation the SSL negotiation mode
254+
* @return a reference to this, so the API can be used fluently
255+
*/
256+
public PgConnectOptions setSslNegotiation(SslNegotiation sslNegotiation) {
257+
this.sslNegotiation = sslNegotiation;
258+
return this;
259+
}
260+
242261
/**
243262
* @return whether the client interacts with a layer 7 proxy instead of a server
244263
*/
@@ -322,6 +341,7 @@ public boolean equals(Object o) {
322341

323342
if (pipeliningLimit != that.pipeliningLimit) return false;
324343
if (sslMode != that.sslMode) return false;
344+
if (sslNegotiation != that.sslNegotiation) return false;
325345

326346
return true;
327347
}
@@ -331,6 +351,7 @@ public int hashCode() {
331351
int result = super.hashCode();
332352
result = 31 * result + pipeliningLimit;
333353
result = 31 * result + sslMode.hashCode();
354+
result = 31 * result + sslNegotiation.hashCode();
334355
return result;
335356
}
336357

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2011-2026 Contributors to the Eclipse Foundation
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7+
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10+
*/
11+
12+
package io.vertx.pgclient;
13+
14+
import io.vertx.codegen.annotations.VertxGen;
15+
16+
/**
17+
* SSL negotiation mode for PostgreSQL connections.
18+
* <p>
19+
* Determines how SSL/TLS is negotiated with the PostgreSQL server.
20+
* This setting controls the negotiation protocol, while {@link SslMode}
21+
* controls whether encryption is required.
22+
*
23+
* @see <a href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-SSLNEGOTIATION">PostgreSQL SSL Negotiation</a>
24+
*/
25+
@VertxGen
26+
public enum SslNegotiation {
27+
28+
/**
29+
* Traditional PostgreSQL SSL negotiation (default).
30+
* <p>
31+
* The client sends an SSL request message and waits for the server's
32+
* response before establishing the SSL connection. This mode works with
33+
* all PostgreSQL versions.
34+
*/
35+
POSTGRES("postgres"),
36+
37+
/**
38+
* Direct SSL negotiation (PostgreSQL 17+).
39+
* <p>
40+
* The client establishes an SSL connection immediately without sending
41+
* an SSL request message. This mode:
42+
* <ul>
43+
* <li>Reduces connection latency by one round-trip</li>
44+
* <li>Enables standard SSL proxies (nginx, haproxy) to terminate TLS</li>
45+
* <li>Supports SNI/ALPN protocol negotiation</li>
46+
* </ul>
47+
* <p>
48+
* <b>Note:</b> Requires PostgreSQL 17 or later. Attempting to use this mode
49+
* with earlier PostgreSQL versions will result in an SSL handshake failure.
50+
*/
51+
DIRECT("direct");
52+
53+
public static final SslNegotiation[] VALUES = SslNegotiation.values();
54+
55+
public final String value;
56+
57+
SslNegotiation(String value) {
58+
this.value = value;
59+
}
60+
61+
/**
62+
* Parse the SSL negotiation mode from a string value.
63+
*
64+
* @param value the string value (case-insensitive)
65+
* @return the corresponding SSL negotiation mode
66+
* @throws IllegalArgumentException if the value is not recognized
67+
*/
68+
public static SslNegotiation of(String value) {
69+
for (SslNegotiation sslNegotiation : VALUES) {
70+
if (sslNegotiation.value.equalsIgnoreCase(value)) {
71+
return sslNegotiation;
72+
}
73+
}
74+
75+
throw new IllegalArgumentException("Could not find an appropriate SSL negotiation mode for the value [" + value + "].");
76+
}
77+
}

0 commit comments

Comments
 (0)