When I pass keystore/truststore arguments, such as
-Djavax.net.ssl.keyStore=$(KEYSTORE) -Djavax.net.ssl.keyStorePassword=$(PASSWORD) -Djavax.net.ssl.trustStore=$(TRUSTSTORE) -Djavax.net.ssl.trustStorePassword=$(TRUSTSTORE_PASSWORD) -Djavax.net.ssl.trustStoreType=$(TRUSTSTORE_TYPE) in the _JAVA_OPTIONS environment variable to the JVM, I expected the WebClient to use the default Java SSLContext.
It does not use the default SSLContext set by the JVM, resulting in handshake_failures.
Pass a similar _JAVA_OPTIONS variable and try to make a request to a trusted server configured for 2-way TLS.
Here's how I am using the web client:
WebClient.create(baseUrl)
.post()
.uri(uriBuilder -> uriBuilder.path(someUrl).build())
// Other irrelevant configurations, e.g. headers, contentType, body, exchange, etc.
.flatMap(response -> {
return response.bodyToMono(Void.class);
})
.block();
0.8.4.RELEASE
java -version)openjdk version "11" 2018-09-25
OpenJDK Runtime Environment 18.9 (build 11+28)
OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)
uname -a)JVM ran in an official openJDK docker container -- openjdk:11-jre-slim
@ealexhaywood Can you try setting -Dio.netty.handler.ssl.noOpenSsl=true
That didn't work :( it still sends credential-less requests.
I'm fine with using an SSL context builder, but it would have been nice to somehow just use the default context provided by the JVM similar to the Apache HttpClient's useSystemProperties()
Did you at least enable the SSL?
https://docs.spring.io/spring/docs/5.1.1.RELEASE/spring-framework-reference/web-reactive.html#webflux-client-builder-reactor
HttpClient httpClient = HttpClient.create().secure(spec ->
spec.sslContext(SslContextBuilder.forClient().build()));
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
Right, that was kind of my point. I didn't think I would have to set an SSL context since the JDK's native web clients URLConnection and HttpClient don't require you to, and was wondering if there was a non-code changing way to do this.
And that did not work unfortunately.
@rstoyanchev What do you think? ^^^ Should there be some SSL configuration enabled by default for WebClient?
We could indeed enable security by default on the WebClient Reactor connector (we've enabled compression already).
It looks like this won't solve this particular issue; looking at Netty's codebase, it doesn't seem Netty is looking at javax.net.ssl system properties when setting up the SSL contexts.
I meant to post back a while back to show how I configured the WebClient for TLS for anyone else running into a similar problem, for what it's worth:
HttpClient httpClient = HttpClient.create().secure(spec ->
{
try {
String keyStoreLocation = System.getProperty("javax.net.ssl.keyStore");
String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword");
String trustStoreLocation = System.getProperty("javax.net.ssl.trustStore");
String trustStorePassword = System.getProperty("javax.net.ssl.trustStorePassword");
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(ResourceUtils.getFile(keyStoreLocation)), keyStorePassword.toCharArray());
// Set up key manager factory to use our key store
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
// truststore
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(new FileInputStream((ResourceUtils.getFile(trustStoreLocation))), trustStorePassword.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
spec.sslContext(SslContextBuilder.forClient()
.keyManager(keyManagerFactory)
.trustManager(trustManagerFactory)
.build());
} catch (Exception e) {
LOGGER.warn("Unable to set SSL Context", e);
}
});
WebClient webClient = WebClient.builder()
.baseUrl(baseUrl)
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
Like I said, not a big deal at all. Just would be nice to have :)
@ealexhaywood Why do you need to do this by yourself? You can delegate that to Netty.
I did the following:
-Djavax.net.ssl.keyStore=<path>/keystore.jks
-Djavax.net.ssl.keyStorePassword=<password>
-Djavax.net.ssl.trustStore=<path>/truststore.ts
-Djavax.net.ssl.trustStorePassword=<password>
-Djavax.net.ssl.trustStoreType=JKS
-Dio.netty.handler.ssl.noOpenSsl=true
WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create()
.secure(spec -> spec.sslContext(SslContextBuilder.forClient()))))
.build()
.get()
.uri("https://localhost:8080/text")
.retrieve()
.bodyToMono(String.class)
.block()
@violetagg Did you pass those arguments as a _JAVA_OPTIONS environment variable? Maybe it works as regular commandline argument.
In our environment there is a strong preference to use environment variables for configuration instead of arguments.
@ealexhaywood JVM options
Right, which is different from setting the _JAVA_OPTIONS variable.
I have tried by providing solution and getting below error for https://localhost:8080
io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: no cipher suites in common
And I used http://locahost:8080 and getting below,
Caused by: io.netty.handler.ssl.NotSslRecordException: not an SSL/TLS record
I have tried by providing solution and getting below error for https://localhost:8080
io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: no cipher suites in common
And I used http://locahost:8080 and getting below,
Caused by: io.netty.handler.ssl.NotSslRecordException: not an SSL/TLS record
Sorry my bad. I have set server.ssl properties in config file which forcing to enable localhost also secure.
@ealexhaywood Why do you need to do this by yourself? You can delegate that to Netty.
I did the following:
-Djavax.net.ssl.keyStore=<path>/keystore.jks -Djavax.net.ssl.keyStorePassword=<password> -Djavax.net.ssl.trustStore=<path>/truststore.ts -Djavax.net.ssl.trustStorePassword=<password> -Djavax.net.ssl.trustStoreType=JKS -Dio.netty.handler.ssl.noOpenSsl=trueWebClient.builder() .clientConnector(new ReactorClientHttpConnector( HttpClient.create() .secure(spec -> spec.sslContext(SslContextBuilder.forClient())))) .build() .get() .uri("https://localhost:8080/text") .retrieve() .bodyToMono(String.class) .block()
Seemes like a good solution, but what should I provide in 'path' for keystore, if my app is spring boot app and it's packed in jar (or if )? How to reference my keystore with relevant path? Seems like classpath:client.jks doesn't work, because TrustStoreManager acts like
String storePropName = System.getProperty("javax.net.ssl.trustStore", jsseDefaultStore);
...
new File(storePropName );
Wondering why they didn't use getResources ...
But it worked in @ealexhaywood manual setup solution, as he uses ResourceUtils.getFile
@ealexhaywood Why do you need to do this by yourself? You can delegate that to Netty.
I did the following:
-Djavax.net.ssl.keyStore=<path>/keystore.jks -Djavax.net.ssl.keyStorePassword=<password> -Djavax.net.ssl.trustStore=<path>/truststore.ts -Djavax.net.ssl.trustStorePassword=<password> -Djavax.net.ssl.trustStoreType=JKS -Dio.netty.handler.ssl.noOpenSsl=trueWebClient.builder() .clientConnector(new ReactorClientHttpConnector( HttpClient.create() .secure(spec -> spec.sslContext(SslContextBuilder.forClient())))) .build() .get() .uri("https://localhost:8080/text") .retrieve() .bodyToMono(String.class) .block()
I tried that, and trustStore gets loaded, however keyStore does not
SSLContextImpl.engineInit() loads system properties if it receives null for trustManager, but it does not do so for keyManager
Most helpful comment
I meant to post back a while back to show how I configured the
WebClientfor TLS for anyone else running into a similar problem, for what it's worth:Like I said, not a big deal at all. Just would be nice to have :)