Skip to content

Commit

Permalink
Merge pull request #41297 from gsmet/3.12.0-backports-2
Browse files Browse the repository at this point in the history
[3.12] 3.12.0 backports 2
  • Loading branch information
gsmet authored Jun 19, 2024
2 parents cd07a9e + ec0e72b commit f383612
Show file tree
Hide file tree
Showing 50 changed files with 1,674 additions and 182 deletions.
2 changes: 1 addition & 1 deletion bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@
<hibernate-quarkus-local-cache.version>0.3.0</hibernate-quarkus-local-cache.version>
<flapdoodle.mongo.version>4.14.0</flapdoodle.mongo.version>
<quarkus-spring-api.version>6.1.SP2</quarkus-spring-api.version>
<quarkus-spring-data-api.version>3.2.SP1</quarkus-spring-data-api.version>
<quarkus-spring-data-api.version>3.2.SP2</quarkus-spring-data-api.version>
<quarkus-spring-security-api.version>6.2</quarkus-spring-security-api.version>
<quarkus-spring-boot-api.version>3.2</quarkus-spring-boot-api.version>
<mockito.version>5.12.0</mockito.version>
Expand Down
1 change: 1 addition & 0 deletions docs/src/main/asciidoc/http-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ include::{generated-dir}/config/quarkus-vertx-http-config-group-access-log-confi
|First line of the request | `%r` | `%{REQUEST_LINE}`
|HTTP status code of the response | `%s` | `%{RESPONSE_CODE}`
|Date and time, in Common Log Format format | `%t` | `%{DATE_TIME}`
|Date and time as defined by a DateTimeFormatter compliant string | | `%{time,date_fime_formatter_string}`
|Remote user that was authenticated | `%u` | `%{REMOTE_USER}`
|Requested URL path | `%U` | `%{REQUEST_URL}`
|Request relative path | `%R` | `%{REQUEST_PATH}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,15 @@ Finally, the `quarkus.http.auth.permission.authenticated` permission is set to t
In this case, all paths are protected by a policy that ensures only `authenticated` users can access them.
For more information, see xref:security-authorize-web-endpoints-reference.adoc[Security Authorization Guide].

[NOTE]
====
When you do not configure a client secret with `quarkus.oidc.credentials.secret`, it is recommended to configure `quarkus.oidc.token-state-manager.encryption-secret`.
The `quarkus.oidc.token-state-manager.encryption-secret` enables the default token state manager to encrypt the user tokens in a browser cookie. If this key is not defined, and the `quarkus.oidc.credentials.secret` fallback is not configured, Quarkus uses a random key. A random key causes existing logins to be invalidated either on application restart or in environment with multiple instances of your application. Alternatively, encryption can also be disabled by setting `quarkus.oidc.token-state-manager.encryption-required` to `false`. However, you should disable secret encryption in development environments only.
The encryption secret is recommended to be 32 chars long. For example, `quarkus.oidc.token-state-manager.encryption-secret=AyM1SysPpbyDfgZld3umj1qzKObwVMk`
====

== Start and configure the Keycloak server

To start a Keycloak server, use Docker and run the following command:
Expand Down
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.
26 changes: 22 additions & 4 deletions docs/src/main/asciidoc/websockets-next-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -610,12 +610,28 @@ Item find(Item item) {
1. Specify the codec to use for both the deserialization of the incoming message
2. Specify the codec to use for the serialization of the outgoing message

== Handle Pong message
== Ping/pong messages

The `@OnPongMessage` annotation is used to consume pong messages.
A websocket endpoint must declare at most one method annotated with `@OnPongMessage`.
A https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2[ping message] may serve as a keepalive or to verify the remote endpoint.
A https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.3[pong message] is sent in response to a ping message and it must have an identical payload.

The method must accept a single parameter of type `Buffer`:
The server automatically responds to a ping message sent from the client.
In other words, there is no need for `@OnPingMessage` callback declared on an endpoint.

The server can send ping messages to a connected client.
The `WebSocketConnection` declares methods to send ping messages; there is a non-blocking variant: `WebSocketConnection#sendPing(Buffer)` and a blocking variant: `WebSocketConnection#sendPingAndAwait(Buffer)`.
By default, the ping messages are not sent automatically.
However, the configuration property `quarkus.websockets-next.server.auto-ping-interval` can be used to set the interval after which, the server sends a ping message to a connected client automatically.

[source,properties]
----
quarkus.websockets-next.server.auto-ping-interval=2 <1>
----
<1> Sends a ping message to a connected client every 2 seconds.

The `@OnPongMessage` annotation is used to define a callback that consumes pong messages sent from the client.
An endpoint must declare at most one method annotated with `@OnPongMessage`.
The callback method must return either `void` or `Uni<Void>`, and it must accept a single parameter of type `Buffer`.

[source,java]
----
Expand All @@ -625,6 +641,8 @@ void pong(Buffer data) {
}
----

NOTE: The server can also send unsolicited pong messages that may serve as a unidirectional heartbeat. There is a non-blocking variant: `WebSocketConnection#sendPong(Buffer)` and also a blocking variant: `WebSocketConnection#sendPongAndAwait(Buffer)`.

[[websocket-next-security]]
== Security

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ public interface HibernateOrmConfigPersistenceUnit {

// @formatter:off
/**
* Path to a file containing the SQL statements to execute when Hibernate ORM starts.
* Paths to files containing the SQL statements to execute when Hibernate ORM starts.
*
* The file is retrieved from the classpath resources,
* so it must be located in the resources directory (e.g. `src/main/resources`).
* The files are retrieved from the classpath resources,
* so they must be located in the resources directory (e.g. `src/main/resources`).
*
* The default value for this setting differs depending on the Quarkus launch mode:
*
Expand Down Expand Up @@ -82,7 +82,7 @@ public interface HibernateOrmConfigPersistenceUnit {
* @asciidoclet
*/
// @formatter:on
@ConfigDocDefault("import.sql in DEV, TEST ; no-file otherwise")
@ConfigDocDefault("import.sql in dev and test modes ; no-file otherwise")
Optional<List<@WithConverter(TrimmedStringConverter.class) String>> sqlLoadScript();

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ public void build(
BuildProducer<ExtensionSslNativeSupportBuildItem> sslNativeSupport) {
final Set<DotName> toRegister = new HashSet<>();

nativeLibs.produce(new NativeImageResourceBuildItem("kafka/kafka-version.properties"));
collectImplementors(toRegister, indexBuildItem, Serializer.class);
collectImplementors(toRegister, indexBuildItem, Deserializer.class);
collectImplementors(toRegister, indexBuildItem, Partitioner.class);
Expand Down
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;
}
}
}
Loading

0 comments on commit f383612

Please sign in to comment.