Skip to content

Commit 112ed97

Browse files
committed
Add customizeSslContext(Consumer<SslContextBuilder>)
SslProvider is now created lazily on connect and we allow customization of the used SslContextBuilder. [resolves #191]
1 parent 71e7ef8 commit 112ed97

7 files changed

+98
-14
lines changed

src/main/java/io/r2dbc/postgresql/PostgresqlConnectionConfiguration.java

+27-6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import java.util.List;
3737
import java.util.Map;
3838
import java.util.ServiceLoader;
39+
import java.util.function.Function;
40+
import java.util.function.Supplier;
3941

4042
import static reactor.netty.tcp.SslProvider.DefaultConfigurationType.TCP;
4143

@@ -277,6 +279,8 @@ public static final class Builder {
277279
@Nullable
278280
private String sslRootCert = null;
279281

282+
private Function<SslContextBuilder, SslContextBuilder> sslContextBuilderCustomizer = Function.identity();
283+
280284
@Nullable
281285
private String username;
282286

@@ -473,6 +477,20 @@ public Builder socket(String socket) {
473477
return this;
474478
}
475479

480+
/**
481+
* Configure a {@link SslContextBuilder} customizer. The customizer gets applied on each SSL connection attempt to allow for just-in-time configuration updates. The {@link Function} gets
482+
* called with the prepared {@link SslContextBuilder} that has all configuration options applied. The customizer may return the same builder or return a new builder instance to be used to
483+
* build the SSL context.
484+
*
485+
* @param sslContextBuilderCustomizer customizer function
486+
* @return this {@link Builder}
487+
* @throws IllegalArgumentException if {@code sslContextBuilderCustomizer} is {@code null}
488+
*/
489+
public Builder sslContextBuilderCustomizer(Function<SslContextBuilder, SslContextBuilder> sslContextBuilderCustomizer) {
490+
this.sslContextBuilderCustomizer = Assert.requireNonNull(sslContextBuilderCustomizer, "sslContextBuilderCustomizer must not be null");
491+
return this;
492+
}
493+
476494
/**
477495
* Configure ssl cert for client certificate authentication.
478496
*
@@ -544,6 +562,7 @@ public String toString() {
544562
", schema='" + this.schema + '\'' +
545563
", username='" + this.username + '\'' +
546564
", socket='" + this.socket + '\'' +
565+
", sslContextBuilderCustomizer='" + this.sslContextBuilderCustomizer + '\'' +
547566
", sslMode='" + this.sslMode + '\'' +
548567
", sslRootCert='" + this.sslRootCert + '\'' +
549568
", sslCert='" + this.sslCert + '\'' +
@@ -577,14 +596,14 @@ public Builder username(String username) {
577596

578597
private SSLConfig createSslConfig() {
579598
if (this.socket != null || this.sslMode == SSLMode.DISABLE) {
580-
return new SSLConfig(SSLMode.DISABLE, null, (hostname, session) -> true);
599+
return SSLConfig.disabled();
581600
}
601+
582602
HostnameVerifier hostnameVerifier = this.sslHostnameVerifier;
583-
SslProvider sslProvider = createSslProvider();
584-
return new SSLConfig(this.sslMode, sslProvider, hostnameVerifier);
603+
return new SSLConfig(this.sslMode, createSslProvider(), hostnameVerifier);
585604
}
586605

587-
private SslProvider createSslProvider() {
606+
private Supplier<SslProvider> createSslProvider() {
588607
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
589608
if (this.sslMode.verifyCertificate()) {
590609
if (this.sslRootCert != null) {
@@ -625,8 +644,10 @@ private SslProvider createSslProvider() {
625644
String sslPassword = this.sslPassword == null ? null : this.sslPassword.toString();
626645
sslContextBuilder.keyManager(new File(sslCert), new File(sslKey), sslPassword);
627646
}
628-
return SslProvider.builder()
629-
.sslContext(sslContextBuilder)
647+
648+
649+
return () -> SslProvider.builder()
650+
.sslContext(this.sslContextBuilderCustomizer.apply(sslContextBuilder))
630651
.defaultConfiguration(TCP)
631652
.build();
632653
}

src/main/java/io/r2dbc/postgresql/PostgresqlConnectionFactoryProvider.java

+11
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package io.r2dbc.postgresql;
1818

19+
import io.netty.handler.ssl.SslContextBuilder;
1920
import io.r2dbc.postgresql.client.SSLMode;
2021
import io.r2dbc.postgresql.util.Assert;
2122
import io.r2dbc.spi.ConnectionFactoryOptions;
@@ -24,6 +25,7 @@
2425

2526
import javax.net.ssl.HostnameVerifier;
2627
import java.util.Map;
28+
import java.util.function.Function;
2729

2830
import static io.r2dbc.spi.ConnectionFactoryOptions.CONNECT_TIMEOUT;
2931
import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE;
@@ -74,6 +76,11 @@ public final class PostgresqlConnectionFactoryProvider implements ConnectionFact
7476
*/
7577
public static final Option<String> SOCKET = Option.valueOf("socket");
7678

79+
/**
80+
* Customizer {@link Function} for {@link SslContextBuilder}.
81+
*/
82+
public static final Option<Function<SslContextBuilder, SslContextBuilder>> SSL_CONTEXT_BUILDER_CUSTOMIZER = Option.valueOf("sslContextBuilderCustomizer");
83+
7784
/**
7885
* Full path for the certificate file.
7986
*/
@@ -216,6 +223,10 @@ static PostgresqlConnectionConfiguration createConfiguration(ConnectionFactoryOp
216223
builder.sslHostnameVerifier((HostnameVerifier) sslHostnameVerifier);
217224
}
218225
}
226+
227+
if (connectionFactoryOptions.hasOption(SSL_CONTEXT_BUILDER_CUSTOMIZER)) {
228+
builder.sslContextBuilderCustomizer(connectionFactoryOptions.getRequiredValue(SSL_CONTEXT_BUILDER_CUSTOMIZER));
229+
}
219230
}
220231

221232
return builder.build();

src/main/java/io/r2dbc/postgresql/client/SSLConfig.java

+17-7
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,19 @@
2222
import reactor.util.annotation.Nullable;
2323

2424
import javax.net.ssl.HostnameVerifier;
25+
import java.util.function.Supplier;
2526

2627
public final class SSLConfig {
2728

29+
@Nullable
2830
private final HostnameVerifier hostnameVerifier;
2931

3032
private final SSLMode sslMode;
3133

32-
private final SslProvider sslProvider;
34+
@Nullable
35+
private final Supplier<SslProvider> sslProvider;
3336

34-
public SSLConfig(SSLMode sslMode, @Nullable SslProvider sslProvider, @Nullable HostnameVerifier hostnameVerifier) {
37+
public SSLConfig(SSLMode sslMode, @Nullable Supplier<SslProvider> sslProvider, @Nullable HostnameVerifier hostnameVerifier) {
3538
if (sslMode != SSLMode.DISABLE) {
3639
Assert.requireNonNull(sslProvider, "Ssl provider is required for ssl mode " + sslMode);
3740
}
@@ -43,16 +46,23 @@ public SSLConfig(SSLMode sslMode, @Nullable SslProvider sslProvider, @Nullable H
4346
this.hostnameVerifier = hostnameVerifier;
4447
}
4548

46-
public HostnameVerifier getHostnameVerifier() {
47-
return hostnameVerifier;
49+
public static SSLConfig disabled() {
50+
return new SSLConfig(SSLMode.DISABLE, null, (hostname, session) -> true);
51+
}
52+
53+
HostnameVerifier getHostnameVerifier() {
54+
return this.hostnameVerifier;
4855
}
4956

5057
public SSLMode getSslMode() {
51-
return sslMode;
58+
return this.sslMode;
5259
}
5360

54-
public SslProvider getSslProvider() {
55-
return sslProvider;
61+
public Supplier<SslProvider> getSslProvider() {
62+
if (this.sslProvider == null) {
63+
throw new IllegalStateException("SSL Mode disabled. SslProvider not available");
64+
}
65+
return this.sslProvider;
5666
}
5767

5868
public SSLConfig mutateMode(SSLMode newMode) {

src/main/java/io/r2dbc/postgresql/client/SSLSessionHandlerAdapter.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ final class SSLSessionHandlerAdapter extends ChannelInboundHandlerAdapter implem
4848
SSLSessionHandlerAdapter(ByteBufAllocator alloc, SSLConfig sslConfig) {
4949
this.alloc = alloc;
5050
this.sslConfig = sslConfig;
51-
this.sslEngine = sslConfig.getSslProvider()
51+
this.sslEngine = sslConfig.getSslProvider().get()
5252
.getSslContext()
5353
.newEngine(alloc);
5454
this.handshakeFuture = new CompletableFuture<>();

src/test/java/io/r2dbc/postgresql/PostgresqlConnectionConfigurationTest.java

+8
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,12 @@ void constructorInvalidOptions() {
148148
.withMessage("option values must not be null");
149149
}
150150

151+
@Test
152+
void constructorNoSslCustomizer() {
153+
assertThatIllegalArgumentException().isThrownBy(() -> PostgresqlConnectionConfiguration.builder()
154+
.sslContextBuilderCustomizer(null)
155+
.build())
156+
.withMessage("sslContextBuilderCustomizer must not be null");
157+
}
158+
151159
}

src/test/java/io/r2dbc/postgresql/PostgresqlConnectionFactoryProviderTest.java

+20
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,16 @@
3030
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.OPTIONS;
3131
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.POSTGRESQL_DRIVER;
3232
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.SOCKET;
33+
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.SSL_CONTEXT_BUILDER_CUSTOMIZER;
34+
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.SSL_MODE;
3335
import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER;
3436
import static io.r2dbc.spi.ConnectionFactoryOptions.HOST;
3537
import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD;
3638
import static io.r2dbc.spi.ConnectionFactoryOptions.SSL;
3739
import static io.r2dbc.spi.ConnectionFactoryOptions.USER;
3840
import static io.r2dbc.spi.ConnectionFactoryOptions.builder;
3941
import static org.assertj.core.api.Assertions.assertThat;
42+
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
4043
import static org.assertj.core.api.Assertions.assertThatThrownBy;
4144

4245
final class PostgresqlConnectionFactoryProviderTest {
@@ -202,6 +205,23 @@ void shouldConfigureAutodetectExtensions() {
202205
assertThat(factory.getConfiguration().isAutodetectExtensions()).isFalse();
203206
}
204207

208+
@Test
209+
void shouldApplySslContextBuilderCustomizer() {
210+
211+
PostgresqlConnectionFactory factory = this.provider.create(builder()
212+
.option(DRIVER, POSTGRESQL_DRIVER)
213+
.option(HOST, "test-host")
214+
.option(PASSWORD, "test-password")
215+
.option(USER, "test-user")
216+
.option(SSL_MODE, SSLMode.ALLOW)
217+
.option(SSL_CONTEXT_BUILDER_CUSTOMIZER, sslContextBuilder -> {
218+
throw new IllegalStateException("Works!");
219+
})
220+
.build());
221+
222+
assertThatIllegalStateException().isThrownBy(() -> factory.getConfiguration().getSslConfig().getSslProvider().get()).withMessageContaining("Works!");
223+
}
224+
205225
@Test
206226
void shouldConnectUsingUnixDomainSocket() {
207227

src/test/java/io/r2dbc/postgresql/client/ReactorNettyClientIntegrationTests.java

+14
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,20 @@ void verifyCa() {
645645
.verifyComplete());
646646
}
647647

648+
@Test
649+
void verifyCaWithCustomizer() {
650+
client(
651+
c -> c.sslContextBuilderCustomizer(sslContextBuilder -> {
652+
return sslContextBuilder.trustManager(new File(SERVER.getServerCrt()))
653+
.keyManager(new File(SERVER.getClientCrt()), new File(SERVER.getClientKey()));
654+
})
655+
.sslMode(SSLMode.VERIFY_CA),
656+
c -> c
657+
.as(StepVerifier::create)
658+
.expectNextCount(1)
659+
.verifyComplete());
660+
}
661+
648662
@Test
649663
void verifyCaFailedWithWrongRootCert() {
650664
client(

0 commit comments

Comments
 (0)