Skip to content

Commit

Permalink
Introduced io.quarkus.restclient.NoopHostnameVerifier
Browse files Browse the repository at this point in the history
This should be used in the rest-client when hostname verification needs to be disabled for a specific client endpoint
Also tests were improved to use what's provided in the rest-client extension
  • Loading branch information
gastaldi committed Nov 1, 2020
1 parent 314fcfe commit 5e4eb66
Show file tree
Hide file tree
Showing 13 changed files with 147 additions and 66 deletions.
10 changes: 10 additions & 0 deletions docs/src/main/asciidoc/rest-client.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,16 @@ country-api/mp-rest/url=https://restcountries.eu/rest #
country-api/mp-rest/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]
----
country-api/mp-rest/hostnameVerifier=io.quarkus.restclient.NoopHostnameVerifier
----

== Update the JAX-RS resource

Open the `src/main/java/org/acme/rest/client/CountriesResource.java` file and update it with the following content:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import io.quarkus.deployment.pkg.PackageConfig;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.restclient.NoopHostnameVerifier;
import io.quarkus.restclient.runtime.RestClientBase;
import io.quarkus.restclient.runtime.RestClientRecorder;
import io.quarkus.resteasy.common.deployment.JaxrsProvidersToRegisterBuildItem;
Expand Down Expand Up @@ -160,7 +161,7 @@ void setup(BuildProducer<FeatureBuildItem> feature,
javax.ws.rs.ext.ReaderInterceptor[].class.getName()));

reflectiveClass.produce(new ReflectiveClassBuildItem(true, false,
ResteasyClientBuilder.class.getName()));
ResteasyClientBuilder.class.getName(), NoopHostnameVerifier.class.getName()));
}

@BuildStep
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.quarkus.restclient;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;

/**
* The {@link NoopHostnameVerifier} essentially turns hostname verification off.
*/
public class NoopHostnameVerifier implements HostnameVerifier {

@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}

@Override
public final String toString() {
return "NO_OP";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@

import javax.net.ssl.HostnameVerifier;

import io.quarkus.arc.Arc;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.runtime.graal.DisabledSSLContext;
import io.quarkus.runtime.ssl.SslContextConfiguration;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.context.ManagedExecutor;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.graalvm.nativeimage.ImageInfo;

import io.quarkus.arc.Arc;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.runtime.graal.DisabledSSLContext;
import io.quarkus.runtime.ssl.SslContextConfiguration;

public class RestClientBase {

public static final String MP_REST = "mp-rest";
Expand Down Expand Up @@ -95,7 +96,7 @@ private void configureSsl(RestClientBuilder builder) {

private void registerHostnameVerifier(String verifier, RestClientBuilder builder) {
try {
Class<?> verifierClass = Class.forName(verifier, true, Thread.currentThread().getContextClassLoader());
Class<?> verifierClass = Thread.currentThread().getContextClassLoader().loadClass(verifier);
builder.hostnameVerifier((HostnameVerifier) verifierClass.getDeclaredConstructor().newInstance());
} catch (NoSuchMethodException e) {
throw new RuntimeException(
Expand All @@ -105,7 +106,8 @@ private void registerHostnameVerifier(String verifier, RestClientBuilder builder
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(
"Failed to instantiate hostname verifier class " + verifier
+ ". Make sure it has a public, no-argument constructor", e);
+ ". Make sure it has a public, no-argument constructor",
e);
} catch (ClassCastException e) {
throw new RuntimeException("The provided hostname verifier " + verifier + " is not an instance of HostnameVerifier",
e);
Expand Down
17 changes: 17 additions & 0 deletions integration-tests/rest-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-fault-tolerance</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
Expand Down Expand Up @@ -74,6 +78,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-fault-tolerance-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.quarkus.it.rest.client.selfsigned;

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

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

@RegisterRestClient(baseUri = "https://self-signed.badssl.com/", configKey = "self-signed")
public interface ExternalSelfSignedClient {

@GET
@Retry(delay = 1000)
Response invoke();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,44 @@
import java.io.IOException;
import java.net.URL;

import javax.inject.Inject;
import javax.net.ssl.HttpsURLConnection;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.rest.client.inject.RestClient;

/**
* This has nothing to do with rest-client, but we add it here in order to avoid creating
* a new integration test that would slow down our CI
*/
@Path("/self-signed")
public class ExternalSelfSignedResource {

@Inject
@RestClient
ExternalSelfSignedClient client;

@GET
@Produces(MediaType.TEXT_PLAIN)
public String perform() throws IOException {
return String.valueOf(client.invoke().getStatus());
}

@GET
@Path("/java")
@Produces(MediaType.TEXT_PLAIN)
public String invokeJavaURLWithDefaultTruststore() throws IOException {
try {
return doGetCipher();
} catch (IOException e) {

// if it fails it might be because the remote service is down, so sleep and try again
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {

}

return doGetCipher();
}
}
Expand All @@ -42,4 +53,5 @@ private String doGetCipher() throws IOException {
con.getResponseCode();
return con.getCipherSuite();
}

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package io.quarkus.it.rest.client.wronghost;

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

@Path("/")
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)
String root();
@Retry(delay = 1000)
Response invoke();
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,25 @@
package io.quarkus.it.rest.client.wronghost;

import java.io.FileInputStream;
import java.net.URL;
import java.security.KeyStore;
import java.io.IOException;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.eclipse.microprofile.rest.client.inject.RestClient;

@Path("/wrong-host")
public class WrongHostResource {

@Inject
@RestClient
WrongHostClient client;

@GET
@Path("/rest-client")
@Produces(MediaType.TEXT_PLAIN)
public String restClient() throws Exception {
KeyStore ks = KeyStore.getInstance("JKS");

// the system props are set in pom.xml and made available for native tests via RestClientTestResource
ks.load(new FileInputStream(System.getProperty("rest-client.trustStore")),
System.getProperty("rest-client.trustStorePassword").toCharArray());

return RestClientBuilder.newBuilder().baseUrl(new URL("https://wrong.host.badssl.com/")).trustStore(ks)
.hostnameVerifier(NoopHostnameVerifier.INSTANCE)
.build(WrongHostClient.class)
.root();
public String perform() throws IOException {
return String.valueOf(client.invoke().getStatus());
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
package io.quarkus.it.rest.client.selfsigned;

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

import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;

@QuarkusTest
public class ExternalSelfSignedTestCase {

@Test
public void included() {
RestAssured.when()
public void should_accept_self_signed_certs() {
when()
.get("/self-signed")
.then()
.statusCode(200)
.body(is("200"));
}

@Test
public void should_accept_self_signed_certs_java_url() {
when()
.get("/self-signed/java")
.then()
.statusCode(200)
.body(is(not(empty())));
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
package io.quarkus.it.rest.client.wronghost;

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

import org.junit.jupiter.api.Test;

import io.quarkus.it.rest.client.RestClientTestResource;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;

@QuarkusTest
@QuarkusTestResource(RestClientTestResource.class)
@QuarkusTestResource(ExternalWrongHostTestResource.class)
public class ExternalWrongHostTestCase {

@Test
public void restClient() {
RestAssured.when()
.get("/wrong-host/rest-client")
when()
.get("/wrong-host")
.then()
.statusCode(200)
.body(is(not(empty())));
.body(is("200"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkus.it.rest.client.wronghost;

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

import io.quarkus.restclient.NoopHostnameVerifier;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;

/**
* The only point of this class is to propagate the properties when running the native tests
*/
public class ExternalWrongHostTestResource 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/hostnameVerifier", NoopHostnameVerifier.class.getName());
return result;
}

@Override
public void stop() {

}
}

0 comments on commit 5e4eb66

Please sign in to comment.