diff --git a/docs/src/main/asciidoc/rest-client.adoc b/docs/src/main/asciidoc/rest-client.adoc index 51631d3ee23da..ea7456117627b 100644 --- a/docs/src/main/asciidoc/rest-client.adoc +++ b/docs/src/main/asciidoc/rest-client.adoc @@ -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, add the following property to your configuration: + +[source,properties] +---- +country-api/mp-rest/hostnameVerifier=io.quarkus.restclient.runtime.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: diff --git a/extensions/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java b/extensions/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java index db86b354fa5e2..9e8b497e50f08 100644 --- a/extensions/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java +++ b/extensions/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java @@ -73,6 +73,7 @@ import io.quarkus.deployment.pkg.PackageConfig; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; +import io.quarkus.restclient.runtime.NoopHostnameVerifier; import io.quarkus.restclient.runtime.RestClientBase; import io.quarkus.restclient.runtime.RestClientRecorder; import io.quarkus.resteasy.common.deployment.JaxrsProvidersToRegisterBuildItem; @@ -160,7 +161,7 @@ void setup(BuildProducer feature, javax.ws.rs.ext.ReaderInterceptor[].class.getName())); reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, - ResteasyClientBuilder.class.getName())); + ResteasyClientBuilder.class.getName(), NoopHostnameVerifier.class.getName())); } @BuildStep diff --git a/extensions/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/NoopHostnameVerifier.java b/extensions/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/NoopHostnameVerifier.java new file mode 100644 index 0000000000000..755331dd541e6 --- /dev/null +++ b/extensions/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/NoopHostnameVerifier.java @@ -0,0 +1,20 @@ +package io.quarkus.restclient.runtime; + +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"; + } +} diff --git a/extensions/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java b/extensions/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java index 96b3f99b5868e..9d402bb45f1e1 100644 --- a/extensions/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java +++ b/extensions/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java @@ -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"; @@ -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( @@ -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); diff --git a/integration-tests/rest-client/pom.xml b/integration-tests/rest-client/pom.xml index 771231c3b5711..b4988b167c67d 100644 --- a/integration-tests/rest-client/pom.xml +++ b/integration-tests/rest-client/pom.xml @@ -31,6 +31,10 @@ io.quarkus quarkus-rest-client + + io.quarkus + quarkus-smallrye-fault-tolerance + org.jboss.resteasy resteasy-multipart-provider @@ -74,6 +78,19 @@ + + io.quarkus + quarkus-smallrye-fault-tolerance-deployment + ${project.version} + pom + test + + + * + * + + + diff --git a/integration-tests/rest-client/src/main/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedClient.java b/integration-tests/rest-client/src/main/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedClient.java new file mode 100644 index 0000000000000..04d41d312c643 --- /dev/null +++ b/integration-tests/rest-client/src/main/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedClient.java @@ -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(); +} diff --git a/integration-tests/rest-client/src/main/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedResource.java b/integration-tests/rest-client/src/main/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedResource.java index f62a6f7d67462..74f49a2c2ff3d 100644 --- a/integration-tests/rest-client/src/main/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedResource.java +++ b/integration-tests/rest-client/src/main/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedResource.java @@ -1,14 +1,15 @@ package io.quarkus.it.rest.client.selfsigned; import java.io.IOException; -import java.net.URL; -import javax.net.ssl.HttpsURLConnection; +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.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 @@ -16,30 +17,13 @@ @Path("/self-signed") public class ExternalSelfSignedResource { + @Inject + @RestClient + ExternalSelfSignedClient client; + @GET @Produces(MediaType.TEXT_PLAIN) public String perform() 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(); - } - } - - private String doGetCipher() throws IOException { - // this URL provides an always on example of an HTTPS URL utilizing self-signed certificate - URL url = new URL("https://self-signed.badssl.com/"); - HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); - con.setRequestMethod("GET"); - con.getResponseCode(); - return con.getCipherSuite(); + return String.valueOf(client.invoke().getStatus()); } } diff --git a/integration-tests/rest-client/src/main/java/io/quarkus/it/rest/client/wronghost/WrongHostClient.java b/integration-tests/rest-client/src/main/java/io/quarkus/it/rest/client/wronghost/WrongHostClient.java index fd4b085347684..1fbf3e8f12676 100644 --- a/integration-tests/rest-client/src/main/java/io/quarkus/it/rest/client/wronghost/WrongHostClient.java +++ b/integration-tests/rest-client/src/main/java/io/quarkus/it/rest/client/wronghost/WrongHostClient.java @@ -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(); } diff --git a/integration-tests/rest-client/src/main/java/io/quarkus/it/rest/client/wronghost/WrongHostResource.java b/integration-tests/rest-client/src/main/java/io/quarkus/it/rest/client/wronghost/WrongHostResource.java index 7e643c53cdcb9..e6cc0b2a51c2e 100644 --- a/integration-tests/rest-client/src/main/java/io/quarkus/it/rest/client/wronghost/WrongHostResource.java +++ b/integration-tests/rest-client/src/main/java/io/quarkus/it/rest/client/wronghost/WrongHostResource.java @@ -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()); } } diff --git a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/RestClientTestResource.java b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/RestClientTestResource.java deleted file mode 100644 index 247c1e301534a..0000000000000 --- a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/RestClientTestResource.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.quarkus.it.rest.client; - -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 RestClientTestResource implements QuarkusTestResourceLifecycleManager { - - @Override - public Map start() { - Map result = new HashMap<>(); - result.put("rest-client.trustStore", System.getProperty("rest-client.trustStore")); - result.put("rest-client.trustStorePassword", System.getProperty("rest-client.trustStorePassword")); - return result; - } - - @Override - public void stop() { - - } -} diff --git a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedTestCase.java b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedTestCase.java index 7b2530e7d93bb..91262baec785a 100644 --- a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedTestCase.java +++ b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedTestCase.java @@ -1,23 +1,21 @@ package io.quarkus.it.rest.client.selfsigned; -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.test.junit.QuarkusTest; -import io.restassured.RestAssured; @QuarkusTest public class ExternalSelfSignedTestCase { @Test public void included() { - RestAssured.when() + when() .get("/self-signed") .then() .statusCode(200) - .body(is(not(empty()))); + .body(is("200")); } } diff --git a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestCase.java b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestCase.java index 004f73d3cf26f..32ca62be1b2f7 100644 --- a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestCase.java +++ b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestCase.java @@ -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")); } } diff --git a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestResource.java b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestResource.java new file mode 100644 index 0000000000000..696f2ac6fdc60 --- /dev/null +++ b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestResource.java @@ -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 ExternalWrongHostTestResource implements QuarkusTestResourceLifecycleManager { + + @Override + public Map start() { + Map 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", "io.quarkus.restclient.runtime.NoopHostnameVerifier"); + return result; + } + + @Override + public void stop() { + + } +}