io.quarkus.vertx.http.runtime.options.TlsCertificateReloader Maven / Gradle / Ivy
package io.quarkus.vertx.http.runtime.options;
import static io.quarkus.vertx.http.runtime.options.HttpServerOptionsUtils.getFileContent;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import java.util.function.Supplier;
import org.jboss.logging.Logger;
import io.quarkus.tls.TlsConfiguration;
import io.quarkus.tls.TlsConfigurationRegistry;
import io.quarkus.vertx.http.runtime.ServerSslConfig;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.net.KeyStoreOptions;
import io.vertx.core.net.PemKeyCertOptions;
import io.vertx.core.net.SSLOptions;
/**
* Utility class to handle TLS certificate reloading.
*/
public class TlsCertificateReloader {
/**
* A structure storing the reload tasks.
*/
private static final List TASKS = new CopyOnWriteArrayList<>();
private static final Logger LOGGER = Logger.getLogger(TlsCertificateReloader.class);
/**
* @throws IllegalArgumentException if any of the configuration is invalid
*/
public static long initCertReloadingAction(Vertx vertx, HttpServer server,
HttpServerOptions options, ServerSslConfig configuration,
TlsConfigurationRegistry registry, Optional tlsConfigurationName) {
if (options == null) {
throw new IllegalArgumentException("Unable to configure TLS reloading - The HTTP server options were not provided");
}
boolean useRegistry = false;
if (tlsConfigurationName.isPresent()) {
useRegistry = true;
} else if (registry.getDefault().isPresent() && registry.getDefault().get().getKeyStoreOptions() != null) {
useRegistry = true;
}
SSLOptions ssl = null;
TlsConfiguration tlsConfiguration = null;
if (!useRegistry) {
ssl = options.getSslOptions();
if (ssl == null) {
throw new IllegalArgumentException("Unable to configure TLS reloading - TLS/SSL is not enabled on the server");
}
} else {
if (tlsConfigurationName.isPresent()) {
tlsConfiguration = registry.get(tlsConfigurationName.get()).orElseThrow();
} else {
tlsConfiguration = registry.getDefault().orElseThrow();
}
}
long period;
// Validation
if (configuration.certificate.reloadPeriod.isPresent()) {
if (configuration.certificate.reloadPeriod.get().toMillis() < 30_000) {
throw new IllegalArgumentException(
"Unable to configure TLS reloading - The reload period cannot be less than 30 seconds");
}
period = configuration.certificate.reloadPeriod.get().toMillis();
} else {
return -1;
}
boolean reloadFromRegistry = useRegistry;
TlsConfiguration registryConfiguration = tlsConfiguration;
SSLOptions nonRegistryOptions = ssl;
Supplier> task = new Supplier>() {
@Override
public CompletionStage get() {
// We are reading files - must be done on a worker thread.
Future future = vertx.executeBlocking(new Callable() {
@Override
public SSLOptions call() throws Exception {
if (reloadFromRegistry) {
if (registryConfiguration.reload()) {
return registryConfiguration.getSSLOptions();
} else {
return null;
}
} else {
var c = reloadFileContent(nonRegistryOptions, configuration);
if (c.equals(nonRegistryOptions)) { // No change, skip the update
return null;
}
return c;
}
}
}, true)
.flatMap(new Function>() {
@Override
public Future apply(SSLOptions res) {
if (res != null) {
return server.updateSSLOptions(res);
} else {
return Future.succeededFuture(false);
}
}
})
.onComplete(new Handler>() {
@Override
public void handle(AsyncResult ar) {
if (ar.failed()) {
LOGGER.error("Unable to reload the TLS certificate, keeping the current one.", ar.cause());
} else {
if (ar.result()) {
LOGGER.debug("TLS certificates updated");
}
// Not updated, no change.
}
}
});
return future.toCompletionStage();
}
};
long id = vertx.setPeriodic(period, new Handler() {
@Override
public void handle(Long id) {
task.get();
}
});
TASKS.add(new ReloadCertificateTask(id, task));
return id;
}
public static void unschedule(Vertx vertx, long id) {
vertx.cancelTimer(id);
for (ReloadCertificateTask task : TASKS) {
if (task.it == id) {
TASKS.remove(task);
break;
}
}
}
/**
* Trigger all the reload tasks.
* This method is NOT part of the public API, and is only used for testing purpose.
*
* @return a Uni that is completed when all the reload tasks have been executed
*/
public static CompletionStage reload() {
@SuppressWarnings("rawtypes")
CompletableFuture[] futures = new CompletableFuture[TASKS.size()];
for (int i = 0; i < TASKS.size(); i++) {
futures[i] = TASKS.get(i).action().get().toCompletableFuture();
}
return CompletableFuture.allOf(futures);
}
private static SSLOptions reloadFileContent(SSLOptions ssl, ServerSslConfig configuration) throws IOException {
var copy = new SSLOptions(ssl);
final List keys = new ArrayList<>();
final List certificates = new ArrayList<>();
configuration.certificate.keyFiles.ifPresent(keys::addAll);
configuration.certificate.files.ifPresent(certificates::addAll);
if (!certificates.isEmpty() && !keys.isEmpty()) {
List certBuffer = new ArrayList<>();
List keysBuffer = new ArrayList<>();
for (Path p : certificates) {
byte[] cert = getFileContent(p);
certBuffer.add(Buffer.buffer(cert));
}
for (Path p : keys) {
byte[] key = getFileContent(p);
keysBuffer.add(Buffer.buffer(key));
}
PemKeyCertOptions opts = new PemKeyCertOptions()
.setCertValues(certBuffer)
.setKeyValues(keysBuffer);
copy.setKeyCertOptions(opts);
} else if (configuration.certificate.keyStoreFile.isPresent()) {
var opts = ((KeyStoreOptions) copy.getKeyCertOptions());
opts.setValue(Buffer.buffer(getFileContent(configuration.certificate.keyStoreFile.get())));
copy.setKeyCertOptions(opts);
}
if (configuration.certificate.trustStoreFile.isPresent()) {
var opts = ((KeyStoreOptions) copy.getKeyCertOptions());
opts.setValue(Buffer.buffer(getFileContent(configuration.certificate.trustStoreFile.get())));
copy.setTrustOptions(opts);
}
return copy;
}
static final class ReloadCertificateTask {
private final long it;
private final Supplier> action;
ReloadCertificateTask(long it, Supplier> action) {
this.it = it;
this.action = action;
}
public long it() {
return it;
}
public Supplier> action() {
return action;
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (obj == null || obj.getClass() != this.getClass())
return false;
var that = (ReloadCertificateTask) obj;
return this.it == that.it &&
Objects.equals(this.action, that.action);
}
@Override
public int hashCode() {
return Objects.hash(it, action);
}
@Override
public String toString() {
return "ReloadCertificateTask[" +
"it=" + it + ", " +
"action=" + action + ']';
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy