Skip to content

Commit

Permalink
Rest Client: Add property to skip hostname verification
Browse files Browse the repository at this point in the history
Before these changes, we only can disable the hostname verification in Rest Client classic by providing the following property:

```
quarkus.rest-client.extensions-api.hostname-verifier=io.quarkus.restclient.NoopHostnameVerifier
```

However, this is not working in Rest Client reactive because setting a hostname verifier strategy is not supported by the Vert-x HTTP Client. 

With these changes, we have added a new property in both Rest Client classic and reactive `quarkus.rest-client.extensions-api.verify-host=true or false`. In Rest Client classic, when disabling the verify host, internally it will add the `NoopHostnameVerifier` strategy. In Rest Client reactive, it will properly configure the Vert.x HTTP client to disable the hostname verification. Therefore, in both Rest Client implementations (classic and reactive), the behaviour is the same. 

Fix quarkusio#27901
  • Loading branch information
Sgitario committed Sep 15, 2022
1 parent 4a2a5cb commit 116731d
Show file tree
Hide file tree
Showing 21 changed files with 182 additions and 10 deletions.
9 changes: 9 additions & 0 deletions docs/src/main/asciidoc/rest-client-reactive.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,15 @@ quarkus.rest-client.extensions-api.url=https://stage.code.quarkus.io/api
quarkus.rest-client.extensions-api.scope=javax.inject.Singleton
----

=== Disabling Hostname Verification

To disable the SSL hostname verification for a specific REST client, add the following property to your configuration:

[source,properties]
----
quarkus.rest-client.extensions-api.verify-host=false
----

== Create the JAX-RS resource

Create the `src/main/java/org/acme/rest/client/ExtensionsResource.java` file with the following content:
Expand Down
14 changes: 13 additions & 1 deletion docs/src/main/asciidoc/rest-client.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,21 @@ To disable the SSL hostname verification for a specific REST client, add the fol

[source,properties]
----
quarkus.rest-client.extensions-api.hostname-verifier=io.quarkus.restclient.NoopHostnameVerifier
quarkus.rest-client.extensions-api.verify-host=false
----

Moreover, you can configure a REST client to use your custom hostname verify strategy. All you need to do is to provide a class that implements the interface `javax.net.ssl.HostnameVerifier` and add the following property to your configuration:

[source,properties]
----
quarkus.rest-client.extensions-api.hostname-verifier=<full qualified custom hostname verifier class name>
----

[NOTE]
====
Quarkus REST client provides an embedded hostname verifier strategy to disable the hostname verification called `io.quarkus.restclient.NoopHostnameVerifier`.
====

=== Disabling SSL verifications

To disable all SSL verifications, add the following property to your configuration:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class RestClientConfig {
EMPTY.proxyPassword = Optional.empty();
EMPTY.nonProxyHosts = Optional.empty();
EMPTY.queryParamStyle = Optional.empty();
EMPTY.verifyHost = Optional.empty();
EMPTY.trustStore = Optional.empty();
EMPTY.trustStorePassword = Optional.empty();
EMPTY.trustStoreType = Optional.empty();
Expand Down Expand Up @@ -134,6 +135,12 @@ public class RestClientConfig {
@ConfigItem
public Optional<QueryParamStyle> queryParamStyle;

/**
* Set whether hostname verification is enabled.
*/
@ConfigItem
public Optional<Boolean> verifyHost;

/**
* The trust store location. Can point to either a classpath resource or a file.
*/
Expand Down Expand Up @@ -246,6 +253,7 @@ public static RestClientConfig load(String configKey) {
instance.proxyPassword = getConfigValue(configKey, "proxy-password", String.class);
instance.nonProxyHosts = getConfigValue(configKey, "non-proxy-hosts", String.class);
instance.queryParamStyle = getConfigValue(configKey, "query-param-style", QueryParamStyle.class);
instance.verifyHost = getConfigValue(configKey, "verify-host", Boolean.class);
instance.trustStore = getConfigValue(configKey, "trust-store", String.class);
instance.trustStorePassword = getConfigValue(configKey, "trust-store-password", String.class);
instance.trustStoreType = getConfigValue(configKey, "trust-store-type", String.class);
Expand Down Expand Up @@ -279,6 +287,7 @@ public static RestClientConfig load(Class<?> interfaceClass) {
instance.proxyPassword = getConfigValue(interfaceClass, "proxy-password", String.class);
instance.nonProxyHosts = getConfigValue(interfaceClass, "non-proxy-hosts", String.class);
instance.queryParamStyle = getConfigValue(interfaceClass, "query-param-style", QueryParamStyle.class);
instance.verifyHost = getConfigValue(interfaceClass, "verify-host", Boolean.class);
instance.trustStore = getConfigValue(interfaceClass, "trust-store", String.class);
instance.trustStorePassword = getConfigValue(interfaceClass, "trust-store-password", String.class);
instance.trustStoreType = getConfigValue(interfaceClass, "trust-store-type", String.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class RestClientFallbackConfigSourceInterceptor extends FallbackConfigSou
CLIENT_PROPERTIES.put("connect-timeout", "connectTimeout");
CLIENT_PROPERTIES.put("read-timeout", "readTimeout");
CLIENT_PROPERTIES.put("hostname-verifier", "hostnameVerifier");
CLIENT_PROPERTIES.put("verify-host", "verifyHost");
CLIENT_PROPERTIES.put("trust-store", "trustStore");
CLIENT_PROPERTIES.put("trust-store-password", "trustStorePassword");
CLIENT_PROPERTIES.put("trust-store-type", "trustStoreType");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ public class RestClientsConfig {
@ConfigItem
public Optional<QueryParamStyle> queryParamStyle;

/**
* Set whether hostname verification is enabled.
*
* Can be overwritten by client-specific settings.
*/
@ConfigItem
public Optional<Boolean> verifyHost;

/**
* The trust store location. Can point to either a classpath resource or a file.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import io.quarkus.arc.Arc;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.restclient.NoopHostnameVerifier;
import io.quarkus.restclient.config.RestClientConfig;
import io.quarkus.restclient.config.RestClientsConfig;

Expand Down Expand Up @@ -149,6 +150,13 @@ protected void configureSsl(RestClientBuilder builder) {
clientConfigByConfigKey().hostnameVerifier, configRoot.hostnameVerifier);
if (hostnameVerifier.isPresent()) {
registerHostnameVerifier(hostnameVerifier.get(), builder);
} else {
// If `verify-host` is disabled, we configure the client using the `NoopHostnameVerifier` verifier.
Optional<Boolean> verifyHost = oneOf(clientConfigByClassName().verifyHost, clientConfigByConfigKey().verifyHost,
configRoot.verifyHost);
if (verifyHost.isPresent() && !verifyHost.get()) {
registerHostnameVerifier(NoopHostnameVerifier.class.getName(), builder);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ public RestClientBuilderImpl sslContext(SSLContext sslContext) {
return this;
}

public RestClientBuilderImpl verifyHost(boolean verifyHost) {
clientBuilder.verifyHost(verifyHost);
return this;
}

@Override
public RestClientBuilderImpl trustStore(KeyStore trustStore) {
clientBuilder.trustStore(trustStore);
Expand Down Expand Up @@ -319,6 +324,7 @@ public <T> T build(Class<T> aClass) throws IllegalStateException, RestClientDefi
.orElse(false);

clientBuilder.trustAll(trustAll);
restClientsConfig.verifyHost.ifPresent(clientBuilder::verifyHost);

String userAgent = (String) getConfiguration().getProperty(QuarkusRestClientProperties.USER_AGENT);
if (userAgent != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ private void configureSsl(RestClientBuilderImpl builder) {
if (maybeHostnameVerifier.isPresent()) {
registerHostnameVerifier(maybeHostnameVerifier.get(), builder);
}

oneOf(clientConfigByClassName().verifyHost, clientConfigByConfigKey().verifyHost, configRoot.verifyHost)
.ifPresent(builder::verifyHost);
}

private void registerHostnameVerifier(String verifier, RestClientBuilder builder) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public class ClientBuilderImpl extends ClientBuilder {

private boolean followRedirects;
private boolean trustAll;
private boolean verifyHost;

private LoggingScope loggingScope;
private Integer loggingBodySize = 100;
Expand Down Expand Up @@ -175,6 +176,7 @@ public ClientImpl build() {
HttpClientOptions options = Optional.ofNullable(configuration.getFromContext(HttpClientOptions.class))
.orElseGet(HttpClientOptions::new);

options.setVerifyHost(verifyHost);
if (trustAll) {
options.setTrustAll(true);
options.setVerifyHost(false);
Expand Down Expand Up @@ -344,6 +346,11 @@ public ClientBuilderImpl trustAll(boolean trustAll) {
return this;
}

public ClientBuilderImpl verifyHost(boolean verifyHost) {
this.verifyHost = verifyHost;
return this;
}

public ClientBuilderImpl setUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
Expand Down
19 changes: 19 additions & 0 deletions integration-tests/rest-client-reactive/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
<properties>
<self-signed.trust-store>${project.build.directory}/self-signed.p12</self-signed.trust-store>
<self-signed.trust-store-password>changeit</self-signed.trust-store-password>
<wrong-host.trust-store>${project.build.directory}/wrong-host.p12</wrong-host.trust-store>
<wrong-host.trust-store-password>changeit</wrong-host.trust-store-password>
</properties>

<!--todo add ssl tests-->
Expand Down Expand Up @@ -193,6 +195,23 @@
<includeCertificates>LEAF</includeCertificates>
</configuration>
</execution>
<execution>
<id>wrong-host-truststore</id>
<phase>generate-test-resources</phase>
<goals>
<goal>generate-truststore</goal>
</goals>
<configuration>
<truststoreFormat>PKCS12</truststoreFormat>
<truststoreFile>${wrong-host.trust-store}</truststoreFile>
<truststorePassword>${wrong-host.trust-store-password}</truststorePassword>
<servers>
<server>wrong.host.badssl.com:443</server>
</servers>
<trustAllCertificates>true</trustAllCertificates>
<includeCertificates>LEAF</includeCertificates>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
import io.quarkus.it.rest.client.main.MyResponseExceptionMapper.MyException;
import io.quarkus.it.rest.client.main.selfsigned.ExternalSelfSignedClient;
import io.quarkus.it.rest.client.main.wronghost.WrongHostClient;
import io.smallrye.mutiny.Uni;
import io.vertx.core.Future;
import io.vertx.core.json.Json;
Expand All @@ -48,6 +49,9 @@ public class ClientCallingResource {
@RestClient
ExternalSelfSignedClient externalSelfSignedClient;

@RestClient
WrongHostClient wrongHostClient;

@Inject
InMemorySpanExporter inMemorySpanExporter;

Expand Down Expand Up @@ -172,6 +176,9 @@ void init(@Observes Router router) {

router.get("/self-signed").blockingHandler(
rc -> rc.response().setStatusCode(200).end(String.valueOf(externalSelfSignedClient.invoke().getStatus())));

router.get("/wrong-host").blockingHandler(
rc -> rc.response().setStatusCode(200).end(String.valueOf(wrongHostClient.invoke().getStatus())));
}

private Future<Void> success(RoutingContext rc, String body) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.quarkus.it.rest.client.main.wronghost;

import javax.ws.rs.GET;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.eclipse.microprofile.faulttolerance.Retry;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@RegisterRestClient(baseUri = "https://wrong.host.badssl.com/", configKey = "wrong-host")
public interface WrongHostClient {

@GET
@Produces(MediaType.TEXT_PLAIN)
@Retry(delay = 1000)
Response invoke();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ w-exception-mapper/mp-rest/url=${test.url}
w-fault-tolerance/mp-rest/url=${test.url}
io.quarkus.it.rest.client.main.ParamClient/mp-rest/url=${test.url}
io.quarkus.it.rest.client.multipart.MultipartClient/mp-rest/url=${test.url}
# HTTPS
# Self-Signed client
quarkus.rest-client.self-signed.trust-store=${self-signed.trust-store}
quarkus.rest-client.self-signed.trust-store-password=${self-signed.trust-store-password}
# Wrong Host client
quarkus.rest-client.wrong-host.trust-store=${wrong-host.trust-store}
quarkus.rest-client.wrong-host.trust-store-password=${wrong-host.trust-store-password}
quarkus.rest-client.wrong-host.verify-host=false
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.quarkus.it.rest.client.wronghost;

import static io.restassured.RestAssured.when;
import static org.hamcrest.Matchers.is;

import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class ExternalWrongHostTestCase {
@Test
public void restClient() {
when()
.get("/wrong-host")
.then()
.statusCode(200)
.body(is("200"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@

import org.junit.jupiter.api.Test;

import io.quarkus.it.rest.client.wronghost.ExternalWrongHostTestResourceUsingHostnameVerifier;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
@QuarkusTestResource(ExternalTlsTrustAllTestResource.class)
@QuarkusTestResource(value = ExternalWrongHostTestResourceUsingHostnameVerifier.class, restrictToAnnotatedClass = true)
public class ExternalTlsTrustAllTestCase {

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@

import org.junit.jupiter.api.Test;

import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
@QuarkusTestResource(ExternalWrongHostTestResource.class)
public class ExternalWrongHostTestCase {
public abstract class BaseExternalWrongHostTestCase {

@Test
public void restClient() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/**
* The only point of this class is to propagate the properties when running the native tests
*/
public class ExternalWrongHostTestResource implements QuarkusTestResourceLifecycleManager {
public class ExternalWrongHostTestResourceUsingHostnameVerifier implements QuarkusTestResourceLifecycleManager {

@Override
public Map<String, String> start() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.quarkus.it.rest.client.wronghost;

import java.util.HashMap;
import java.util.Map;

import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;

/**
* The only point of this class is to propagate the properties when running the native tests
*/
public class ExternalWrongHostTestResourceUsingVerifyHost implements QuarkusTestResourceLifecycleManager {

@Override
public Map<String, String> start() {
Map<String, String> result = new HashMap<>();
result.put("wrong-host/mp-rest/trustStore", System.getProperty("rest-client.trustStore"));
result.put("wrong-host/mp-rest/trustStorePassword", System.getProperty("rest-client.trustStorePassword"));
result.put("wrong-host/mp-rest/verifyHost", Boolean.FALSE.toString());
return result;
}

@Override
public void stop() {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
import io.quarkus.test.junit.QuarkusIntegrationTest;

@QuarkusIntegrationTest
public class ExternalWrongHostIT extends ExternalWrongHostTestCase {
public class ExternalWrongHostUsingHostnameVerifierIT extends ExternalWrongHostUsingHostnameVerifierTestCase {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.quarkus.it.rest.client.wronghost;

import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
@QuarkusTestResource(value = ExternalWrongHostTestResourceUsingHostnameVerifier.class, restrictToAnnotatedClass = true)
public class ExternalWrongHostUsingHostnameVerifierTestCase extends BaseExternalWrongHostTestCase {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.quarkus.it.rest.client.wronghost;

import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
@QuarkusTestResource(value = ExternalWrongHostTestResourceUsingVerifyHost.class, restrictToAnnotatedClass = true)
public class ExternalWrongHostUsingVerifyHostTestCase extends BaseExternalWrongHostTestCase {
}

0 comments on commit 116731d

Please sign in to comment.