Skip to content

Commit

Permalink
Merge pull request #27956 from Sgitario/skip_verify_host
Browse files Browse the repository at this point in the history
Rest Client: Add property to skip hostname verification
  • Loading branch information
geoand authored Dec 5, 2022
2 parents 00c512a + 941d3a6 commit 032d694
Show file tree
Hide file tree
Showing 21 changed files with 190 additions and 10 deletions.
13 changes: 13 additions & 0 deletions docs/src/main/asciidoc/rest-client-reactive.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,19 @@ 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
----
[WARNING]
====
This setting should not be used in production as it will disable the SSL hostname verification.
====

== Create the JAX-RS resource

Create the `src/main/java/org/acme/rest/client/ExtensionsResource.java` file with the following content:
Expand Down
18 changes: 17 additions & 1 deletion docs/src/main/asciidoc/rest-client.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,24 @@ 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
----
[WARNING]
====
This setting should not be used in production as it will disable the SSL hostname verification.
====

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

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 @@ -59,6 +59,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 @@ -178,6 +179,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 @@ -347,6 +349,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 @@ -175,6 +177,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 032d694

Please sign in to comment.