Skip to content

Commit

Permalink
Introduce support for the TLS Registry in the REST Client
Browse files Browse the repository at this point in the history
Closes: #41005
  • Loading branch information
geoand committed Jun 12, 2024
1 parent 3c6e8f9 commit 586472c
Show file tree
Hide file tree
Showing 17 changed files with 593 additions and 39 deletions.
2 changes: 1 addition & 1 deletion docs/src/main/asciidoc/tls-registry-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -518,4 +518,4 @@ When the application starts, the TLS registry performs some checks to ensure the
- the cipher suites and protocols are valid
- the CRLs are valid

If any of these checks fail, the application will fail to start.
If any of these checks fail, the application will fail to start.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public class RestClientConfig {
EMPTY.keyStorePassword = Optional.empty();
EMPTY.keyStoreType = Optional.empty();
EMPTY.hostnameVerifier = Optional.empty();
EMPTY.tlsConfigurationName = Optional.empty();
EMPTY.connectionTTL = Optional.empty();
EMPTY.connectionPoolSize = Optional.empty();
EMPTY.keepAliveEnabled = Optional.empty();
Expand Down Expand Up @@ -201,6 +202,20 @@ public class RestClientConfig {
@ConfigItem
public Optional<String> hostnameVerifier;

/**
* The name of the TLS configuration to use.
* <p>
* If not set and the default TLS configuration is configured ({@code quarkus.tls.*}) then that will be used.
* If a name is configured, it uses the configuration from {@code quarkus.tls.<name>.*}
* If a name is configured, but no TLS configuration is found with that name then an error will be thrown.
* <p>
* If no TLS configuration is set, then the keys-tore, trust-store, etc. properties will be used.
* <p>
* This property is not applicable to the RESTEasy Client.
*/
@ConfigItem
public Optional<String> tlsConfigurationName;

/**
* The time in ms for which a connection remains unused in the connection pool before being evicted and closed.
* A timeout of {@code 0} means there is no timeout.
Expand Down Expand Up @@ -317,6 +332,7 @@ public static RestClientConfig load(String configKey) {
instance.keyStorePassword = getConfigValue(configKey, "key-store-password", String.class);
instance.keyStoreType = getConfigValue(configKey, "key-store-type", String.class);
instance.hostnameVerifier = getConfigValue(configKey, "hostname-verifier", String.class);
instance.tlsConfigurationName = getConfigValue(configKey, "tls-configuration-name", String.class);
instance.connectionTTL = getConfigValue(configKey, "connection-ttl", Integer.class);
instance.connectionPoolSize = getConfigValue(configKey, "connection-pool-size", Integer.class);
instance.keepAliveEnabled = getConfigValue(configKey, "keep-alive-enabled", Boolean.class);
Expand Down Expand Up @@ -358,6 +374,7 @@ public static RestClientConfig load(Class<?> interfaceClass) {
instance.keyStorePassword = getConfigValue(interfaceClass, "key-store-password", String.class);
instance.keyStoreType = getConfigValue(interfaceClass, "key-store-type", String.class);
instance.hostnameVerifier = getConfigValue(interfaceClass, "hostname-verifier", String.class);
instance.tlsConfigurationName = getConfigValue(interfaceClass, "tls-configuration-name", String.class);
instance.connectionTTL = getConfigValue(interfaceClass, "connection-ttl", Integer.class);
instance.connectionPoolSize = getConfigValue(interfaceClass, "connection-pool-size", Integer.class);
instance.keepAliveEnabled = getConfigValue(interfaceClass, "keep-alive-enabled", Boolean.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class RestClientFallbackConfigSourceInterceptor extends FallbackConfigSou
CLIENT_PROPERTIES.put("key-store", "keyStore");
CLIENT_PROPERTIES.put("key-store-password", "keyStorePassword");
CLIENT_PROPERTIES.put("key-store-type", "keyStoreType");
CLIENT_PROPERTIES.put("tls-configuration-name", "tlsConfigurationName");
CLIENT_PROPERTIES.put("follow-redirects", "followRedirects");
CLIENT_PROPERTIES.put("proxy-address", "proxyAddress");
CLIENT_PROPERTIES.put("query-param-style", "queryParamStyle");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,20 @@ public class RestClientsConfig {
@ConfigItem
public Optional<String> keyStoreType;

/**
* The name of the TLS configuration to use.
* <p>
* If not set and the default TLS configuration is configured ({@code quarkus.tls.*}) then that will be used.
* If a name is configured, it uses the configuration from {@code quarkus.tls.<name>.*}
* If a name is configured, but no TLS configuration is found with that name then an error will be thrown.
* <p>
* If no TLS configuration is set, then the keys-tore, trust-store, etc. properties will be used.
* <p>
* This property is not applicable to the RESTEasy Client.
*/
@ConfigItem
public Optional<String> tlsConfigurationName;

/**
* If this is true then HTTP/2 will be enabled.
*/
Expand Down
9 changes: 9 additions & 0 deletions extensions/resteasy-reactive/rest-client/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-config-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-tls-registry-deployment</artifactId>
</dependency>
<!-- test dependencies: -->
<dependency>
<groupId>io.quarkus</groupId>
Expand Down Expand Up @@ -98,6 +102,11 @@
<artifactId>stork-service-discovery-static-list</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>me.escoffier.certs</groupId>
<artifactId>certificate-generator-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

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

import static org.assertj.core.api.Assertions.assertThat;

import java.io.File;

import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context;

import org.assertj.core.api.Assertions;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.vertx.ext.web.RoutingContext;
import me.escoffier.certs.Format;
import me.escoffier.certs.junit5.Certificate;
import me.escoffier.certs.junit5.Certificates;

@Certificates(baseDir = "target/certs", certificates = @Certificate(name = "mtls-test", password = "secret", formats = {
Format.JKS, Format.PKCS12, Format.PEM }, client = true))
public class MtlsConfigFromRegistryCdiTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(Client.class, Resource.class)
.addAsResource(new File("target/certs/mtls-test-keystore.p12"), "server-keystore.p12")
.addAsResource(new File("target/certs/mtls-test-server-truststore.p12"), "server-truststore.p12")
.addAsResource(new File("target/certs/mtls-test-client-keystore.p12"), "client-keystore.p12")
.addAsResource(new File("target/certs/mtls-test-client-truststore.p12"), "client-truststore.p12"))

.overrideConfigKey("quarkus.tls.server.key-store.p12.path", "server-keystore.p12")
.overrideConfigKey("quarkus.tls.server.key-store.p12.password", "secret")
.overrideConfigKey("quarkus.tls.server.trust-store.p12.path", "server-truststore.p12")
.overrideConfigKey("quarkus.tls.server.trust-store.p12.password", "secret")
.overrideConfigKey("quarkus.http.tls-configuration-name", "server")

.overrideConfigKey("quarkus.tls.rest-client.key-store.p12.path", "client-keystore.p12")
.overrideConfigKey("quarkus.tls.rest-client.key-store.p12.password", "secret")
.overrideConfigKey("quarkus.tls.rest-client.trust-store.p12.path", "client-truststore.p12")
.overrideConfigKey("quarkus.tls.rest-client.trust-store.p12.password", "secret")
.overrideConfigKey("quarkus.rest-client.rc.url", "https://localhost:${quarkus.http.test-ssl-port:8444}")
.overrideConfigKey("quarkus.rest-client.rc.tls-configuration-name", "rest-client");

@RestClient
Client client;

@Test
void shouldHello() {
assertThat(client.echo("w0rld")).isEqualTo("hello, w0rld");
}

@Path("/hello")
@RegisterRestClient(configKey = "rc")
public interface Client {
@POST
String echo(String name);
}

@Path("/hello")
public static class Resource {
@POST
public String echo(String name, @Context RoutingContext rc) {
Assertions.assertThat(rc.request().connection().isSsl()).isTrue();
Assertions.assertThat(rc.request().isSSL()).isTrue();
Assertions.assertThat(rc.request().connection().sslSession()).isNotNull();
return "hello, " + name;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.quarkus.rest.client.reactive.tls;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.File;

import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context;

import org.assertj.core.api.Assertions;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.vertx.ext.web.RoutingContext;
import me.escoffier.certs.Format;
import me.escoffier.certs.junit5.Certificate;
import me.escoffier.certs.junit5.Certificates;

@Certificates(baseDir = "target/certs", certificates = @Certificate(name = "tls-test", password = "secret", formats = {
Format.JKS, Format.PKCS12, Format.PEM }))
public class TlsConfigFromPropertiesCdiTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(Client.class, Resource.class)
.addAsResource(new File("target/certs/tls-test-keystore.jks"), "keystore.jks")
.addAsResource(new File("target/certs/tls-test-truststore.jks"), "truststore.jks"))
.overrideConfigKey("quarkus.tls.key-store.jks.path", "keystore.jks")
.overrideConfigKey("quarkus.tls.key-store.jks.password", "secret")

.overrideConfigKey("quarkus.rest-client.rc.url", "https://localhost:${quarkus.http.test-ssl-port:8444}")
.overrideConfigKey("quarkus.rest-client.rc.trust-store", "classpath:truststore.jks")
.overrideConfigKey("quarkus.rest-client.rc.trust-store-password", "secret");

@RestClient
Client client;

@Test
void shouldHello() {
assertThat(client.echo("w0rld")).isEqualTo("hello, w0rld");
}

@Path("/hello")
@RegisterRestClient(configKey = "rc")
public interface Client {
@POST
String echo(String name);
}

@Path("/hello")
public static class Resource {
@POST
public String echo(String name, @Context RoutingContext rc) {
Assertions.assertThat(rc.request().connection().isSsl()).isTrue();
Assertions.assertThat(rc.request().isSSL()).isTrue();
Assertions.assertThat(rc.request().connection().sslSession()).isNotNull();
return "hello, " + name;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.quarkus.rest.client.reactive.tls;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.File;

import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context;

import org.assertj.core.api.Assertions;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.vertx.ext.web.RoutingContext;
import me.escoffier.certs.Format;
import me.escoffier.certs.junit5.Certificate;
import me.escoffier.certs.junit5.Certificates;

@Certificates(baseDir = "target/certs", certificates = @Certificate(name = "tls-test", password = "secret", formats = {
Format.JKS, Format.PKCS12, Format.PEM }))
public class TlsConfigFromRegistryCdiTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(Client.class, Resource.class)
.addAsResource(new File("target/certs/tls-test-keystore.jks"), "keystore.jks")
.addAsResource(new File("target/certs/tls-test-truststore.jks"), "truststore.jks"))
.overrideConfigKey("quarkus.tls.key-store.jks.path", "keystore.jks")
.overrideConfigKey("quarkus.tls.key-store.jks.password", "secret")
.overrideConfigKey("quarkus.tls.rest-client.trust-store.jks.path", "truststore.jks")
.overrideConfigKey("quarkus.tls.rest-client.trust-store.jks.password", "secret")

.overrideConfigKey("quarkus.rest-client.rc.url", "https://localhost:${quarkus.http.test-ssl-port:8444}")
.overrideConfigKey("quarkus.rest-client.rc.tls-configuration-name", "rest-client");

@RestClient
Client client;

@Test
void shouldHello() {
assertThat(client.echo("w0rld")).isEqualTo("hello, w0rld");
}

@Path("/hello")
@RegisterRestClient(configKey = "rc")
public interface Client {
@POST
String echo(String name);
}

@Path("/hello")
public static class Resource {
@POST
public String echo(String name, @Context RoutingContext rc) {
Assertions.assertThat(rc.request().connection().isSsl()).isTrue();
Assertions.assertThat(rc.request().isSSL()).isTrue();
Assertions.assertThat(rc.request().connection().sslSession()).isNotNull();
return "hello, " + name;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.quarkus.rest.client.reactive.tls;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.File;
import java.net.URL;
import java.util.Optional;

import jakarta.inject.Inject;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.http.TestHTTPResource;
import io.quarkus.tls.TlsConfiguration;
import io.quarkus.tls.TlsConfigurationRegistry;
import io.vertx.ext.web.RoutingContext;
import me.escoffier.certs.Format;
import me.escoffier.certs.junit5.Certificate;
import me.escoffier.certs.junit5.Certificates;

@Certificates(baseDir = "target/certs", certificates = @Certificate(name = "tls-test", password = "secret", formats = {
Format.JKS, Format.PKCS12, Format.PEM }))
public class TlsConfigFromRegistryManualTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(Client.class, Resource.class)
.addAsResource(new File("target/certs/tls-test-keystore.jks"), "keystore.jks")
.addAsResource(new File("target/certs/tls-test-truststore.jks"), "truststore.jks"))
.overrideConfigKey("quarkus.tls.key-store.jks.path", "keystore.jks")
.overrideConfigKey("quarkus.tls.key-store.jks.password", "secret")
.overrideConfigKey("quarkus.tls.rest-client.trust-store.jks.path", "truststore.jks")
.overrideConfigKey("quarkus.tls.rest-client.trust-store.jks.password", "secret");

@TestHTTPResource(tls = true)
URL url;

@Inject
TlsConfigurationRegistry registry;

@Test
void shouldHello() {
Optional<TlsConfiguration> maybeTlsConfiguration = TlsConfiguration.from(registry, Optional.of("rest-client"));
assertThat(maybeTlsConfiguration).isPresent();
Client client = QuarkusRestClientBuilder.newBuilder().baseUrl(url).tlsConfiguration(maybeTlsConfiguration.get())
.build(Client.class);
assertThat(client.echo("w0rld")).isEqualTo("hello, w0rld");
}

@Path("/hello")
public interface Client {
@POST
String echo(String name);
}

@Path("/hello")
public static class Resource {
@POST
public String echo(String name, @Context RoutingContext rc) {
assertThat(rc.request().connection().isSsl()).isTrue();
assertThat(rc.request().isSSL()).isTrue();
assertThat(rc.request().connection().sslSession()).isNotNull();
return "hello, " + name;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.quarkus.rest.client.reactive.ssl;
package io.quarkus.rest.client.reactive.tls;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
Expand Down
Loading

0 comments on commit 586472c

Please sign in to comment.