Skip to content

Commit 9cd2670

Browse files
committed
Added example for reloading ssl with quarkus
1 parent fd02846 commit 9cd2670

File tree

14 files changed

+292
-3
lines changed

14 files changed

+292
-3
lines changed

README.MD

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ A repository containing different java tutorials
2828
- [Instant Server SSL Reloading with Netty](instant-server-ssl-reloading-with-netty/netty-server)
2929
- [Instant Server SSL Reloading with gRPC](grpc-client-server-with-ssl/instant-server-ssl-reloading-with-grpc)
3030
- [Instant Server SSL Reloading with Spring Boot and Jetty and Database](instant-ssl-reloading-with-spring-jetty-database)
31+
- [Instant Server SSL Reloading with Quarkus](instant-server-ssl-reloading-with-quarkus)
3132
- [gRPC with SSL for Client and Server](grpc-client-server-with-ssl)
3233
- [ElasticSearch with SSL](elasticsearch-with-ssl)
3334
- [WebSocket Client with SSL](websocket-client-with-ssl)

grpc-client-server-with-ssl/instant-server-ssl-reloading-with-grpc/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Open the certificate details in your browser by clicking on the lock logo (on Ch
3535
Please note down the expiration date. Afterwords you will compare it when you have run the admin application.
3636

3737
#### Refresh the server certificates
38-
Wait for one minute and if the keystores are found and have been updated, than the ssl configuration will be updated.
38+
Wait for one minute and if the keystores are found and have been updated, then the ssl configuration will be updated.
3939
Refresh your browser tab and open the certificate details again and compare the expiration date with the one you have noted down.
4040
You should have a similar certificate detail as shown below:
4141

instant-server-ssl-reloading-with-netty/netty-server/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Open the certificate details in your browser by clicking on the lock logo (on Ch
3535
Please note down the expiration date. Afterwords you will compare it when you have run the admin application.
3636

3737
#### Refresh the server certificates
38-
Wait for one minute and if the keystores are found and have been updated, than the ssl configuration will be updated.
38+
Wait for one minute and if the keystores are found and have been updated, then the ssl configuration will be updated.
3939
Refresh your browser tab and open the certificate details again and compare the expiration date with the one you have noted down.
4040
You should have a similar certificate detail as shown below:
4141

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Instant SSL Reloading 🔐
2+
A server configured with ssl has a key material and trust material. These materials are generated from a keypair and certificate which always have an expiration date.
3+
In a traditional server configuration a reboot of the server is required to apply the latest key material and trust material changes when the keystore and truststore are changed.
4+
A downtime is therefore unavoidable. This project demonstrates with a basic setup how update the server certificate from an external source without the need of restarting your server. In this way you can achieve zero downtime.
5+
6+
The repository contains:
7+
- Server, based on Quarkus
8+
9+
### SSL Updating entrypoint for the server:
10+
The server has currently one way to update the existing ssl material:
11+
- File based aka file change listener, see here for the implementation: [FilesBasedSslUpdateService](src/main/java/nl/altindag/server/service/FileBasedSslUpdateService.java)
12+
13+
#### Requirements
14+
- Java 11
15+
- Terminal
16+
17+
#### Configure SSL Update Service
18+
The server will check every minute for changes on the ssl material, in this case the `identity.jks` and `truststore.jks`. You will need to specify where the service should look for these files. Please change the properties below in [FileBasedSslUpdateService](src/main/java/nl/altindag/server/service/FileBasedSslUpdateService.java).
19+
```java
20+
private static final Path identityPath = Path.of("/path/to/your/identity.jks");
21+
private static final Path trustStorePath = Path.of("/path/to/your/truststore.jks");
22+
private static final char[] identityPassword = "secret".toCharArray();
23+
private static final char[] trustStorePassword = "secret".toCharArray();
24+
```
25+
26+
#### Start the server
27+
1. run `mvn clean install` on the [root directory of this repository](https://github.com/Hakky54/java-tutorials/)
28+
2. run `mvn quarkus:dev` on the [quarkus-server maven module](.)
29+
30+
Visit the server with the following url on your browser: https://localhost:8443/hello
31+
Open the certificate details in your browser by clicking on the lock logo (on Chrome). You will see a similar certificate detail as shown below:
32+
33+
![alt text](https://github.com/Hakky54/java-tutorials/blob/main/instant-server-ssl-reloading-with-vertx/vertx-server/images/before-reloading.png?raw=true)
34+
35+
Please note down the expiration date. Afterwords you will compare it when you have run the admin application.
36+
37+
#### Refresh the server certificates
38+
Wait for one minute and if the keystores are found and have been updated, then the ssl configuration will be updated.
39+
Refresh your browser tab and open the certificate details again and compare the expiration date with the one you have noted down.
40+
You should have a similar certificate detail as shown below:
41+
42+
![alt text](https://github.com/Hakky54/java-tutorials/blob/main/instant-server-ssl-reloading-with-quarkus/images/after-reloading.png?raw=true)
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>io.github.hakky54</groupId>
8+
<artifactId>java-tutorials</artifactId>
9+
<version>1.0.0-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>instant-server-ssl-reloading-with-quarkus</artifactId>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>io.github.hakky54</groupId>
17+
<artifactId>sslcontext-kickstart</artifactId>
18+
<version>${version.sslcontext-kickstart}</version>
19+
</dependency>
20+
21+
<dependency>
22+
<groupId>io.quarkus</groupId>
23+
<artifactId>quarkus-resteasy-reactive</artifactId>
24+
</dependency>
25+
</dependencies>
26+
27+
<dependencyManagement>
28+
<dependencies>
29+
<dependency>
30+
<groupId>io.quarkus</groupId>
31+
<artifactId>quarkus-bom</artifactId>
32+
<version>${version.quarkus}</version>
33+
<type>pom</type>
34+
<scope>import</scope>
35+
</dependency>
36+
</dependencies>
37+
</dependencyManagement>
38+
39+
<build>
40+
<plugins>
41+
<plugin>
42+
<groupId>io.quarkus</groupId>
43+
<artifactId>quarkus-maven-plugin</artifactId>
44+
<version>${version.quarkus}</version>
45+
<extensions>true</extensions>
46+
<executions>
47+
<execution>
48+
<goals>
49+
<goal>build</goal>
50+
<goal>generate-code</goal>
51+
</goals>
52+
</execution>
53+
</executions>
54+
</plugin>
55+
</plugins>
56+
</build>
57+
58+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2022 Thunderberry.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package nl.altindag.server.config;
17+
18+
import io.quarkus.vertx.http.HttpServerOptionsCustomizer;
19+
import io.vertx.core.http.HttpServerOptions;
20+
import io.vertx.core.net.KeyCertOptions;
21+
import io.vertx.core.net.TrustOptions;
22+
import jakarta.enterprise.context.ApplicationScoped;
23+
import jakarta.inject.Inject;
24+
import nl.altindag.server.service.FileBasedSslUpdateService;
25+
import nl.altindag.ssl.SSLFactory;
26+
import org.jboss.logging.Logger;
27+
28+
import java.net.URISyntaxException;
29+
import java.nio.file.Paths;
30+
import java.util.concurrent.Executors;
31+
import java.util.concurrent.TimeUnit;
32+
33+
@ApplicationScoped
34+
public class ServerConfig implements HttpServerOptionsCustomizer {
35+
36+
private final Logger LOGGER;
37+
38+
@Inject
39+
public ServerConfig(Logger logger) {
40+
LOGGER = logger;
41+
}
42+
43+
@Override
44+
public void customizeHttpsServer(HttpServerOptions options) {
45+
try {
46+
// Example usage of initially loading the keystores from the classpath
47+
// as absolute path within quarkus. Getting the files with class.getResource()
48+
// is not needed if you are loading your files from the file system.
49+
var identityUrl = ServerConfig.class.getResource("/identity.jks");
50+
var trustUrl = ServerConfig.class.getResource("/truststore.jks");
51+
52+
var sslFactory = SSLFactory.builder()
53+
.withSwappableIdentityMaterial()
54+
.withIdentityMaterial(Paths.get(identityUrl.toURI()), "secret".toCharArray())
55+
.withSwappableTrustMaterial()
56+
.withTrustMaterial(Paths.get(trustUrl.toURI()), "secret".toCharArray())
57+
.build();
58+
59+
var sslUpdateService = new FileBasedSslUpdateService(sslFactory);
60+
61+
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(sslUpdateService::updateSslMaterial, 1, 1, TimeUnit.MINUTES);
62+
LOGGER.info("Checking every minute for changes on the keystore and truststore files");
63+
64+
options.setSsl(true)
65+
.setKeyCertOptions(KeyCertOptions.wrap(sslFactory.getKeyManager().orElseThrow()))
66+
.setTrustOptions(TrustOptions.wrap(sslFactory.getTrustManager().orElseThrow()));
67+
68+
HttpServerOptionsCustomizer.super.customizeHttpsServer(options);
69+
} catch (URISyntaxException e) {
70+
throw new RuntimeException(e);
71+
}
72+
}
73+
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2022 Thunderberry.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package nl.altindag.server.resource;
17+
18+
import jakarta.ws.rs.GET;
19+
import jakarta.ws.rs.Path;
20+
import jakarta.ws.rs.Produces;
21+
import jakarta.ws.rs.core.MediaType;
22+
23+
@Path("/hello")
24+
public class GreetingResource {
25+
26+
@GET
27+
@Produces(MediaType.TEXT_PLAIN)
28+
public String hello() {
29+
return "Hello World!";
30+
}
31+
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2022 Thunderberry.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package nl.altindag.server.service;
17+
18+
import nl.altindag.ssl.SSLFactory;
19+
import nl.altindag.ssl.util.SSLFactoryUtils;
20+
import org.jboss.logging.Logger;
21+
22+
import java.io.IOException;
23+
import java.io.UncheckedIOException;
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
26+
import java.nio.file.attribute.BasicFileAttributes;
27+
import java.time.Instant;
28+
import java.time.ZoneOffset;
29+
import java.time.ZonedDateTime;
30+
31+
public class FileBasedSslUpdateService {
32+
33+
private static final Logger LOGGER = Logger.getLogger(FileBasedSslUpdateService.class);
34+
35+
private static final Path identityPath = Path.of("/path/to/your/identity.jks");
36+
private static final Path trustStorePath = Path.of("/path/to/your/truststore.jks");
37+
private static final char[] identityPassword = "secret".toCharArray();
38+
private static final char[] trustStorePassword = "secret".toCharArray();
39+
40+
private ZonedDateTime lastModifiedTimeIdentityStore = ZonedDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
41+
private ZonedDateTime lastModifiedTimeTrustStore = ZonedDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
42+
43+
private final SSLFactory baseSslFactory;
44+
45+
public FileBasedSslUpdateService(SSLFactory baseSslFactory) {
46+
this.baseSslFactory = baseSslFactory;
47+
LOGGER.info("Started listening for any changes on the keystore and truststore files...");
48+
}
49+
50+
public void updateSslMaterial() {
51+
try {
52+
if (Files.exists(identityPath) && Files.exists(trustStorePath)) {
53+
BasicFileAttributes identityAttributes = Files.readAttributes(identityPath, BasicFileAttributes.class);
54+
BasicFileAttributes trustStoreAttributes = Files.readAttributes(trustStorePath, BasicFileAttributes.class);
55+
56+
boolean identityUpdated = lastModifiedTimeIdentityStore.isBefore(ZonedDateTime.ofInstant(identityAttributes.lastModifiedTime().toInstant(), ZoneOffset.UTC));
57+
boolean trustStoreUpdated = lastModifiedTimeTrustStore.isBefore(ZonedDateTime.ofInstant(trustStoreAttributes.lastModifiedTime().toInstant(), ZoneOffset.UTC));
58+
59+
if (identityUpdated && trustStoreUpdated) {
60+
LOGGER.info("Keystore files have been changed. Trying to read the file content and preparing to update the ssl material");
61+
62+
SSLFactory updatedSslFactory = SSLFactory.builder()
63+
.withIdentityMaterial(identityPath, identityPassword)
64+
.withTrustMaterial(trustStorePath, trustStorePassword)
65+
.build();
66+
67+
SSLFactoryUtils.reload(baseSslFactory, updatedSslFactory);
68+
69+
lastModifiedTimeIdentityStore = ZonedDateTime.ofInstant(identityAttributes.lastModifiedTime().toInstant(), ZoneOffset.UTC);
70+
lastModifiedTimeTrustStore = ZonedDateTime.ofInstant(trustStoreAttributes.lastModifiedTime().toInstant(), ZoneOffset.UTC);
71+
72+
LOGGER.info("Updating ssl material finished");
73+
}
74+
}
75+
} catch (IOException e) {
76+
throw new UncheckedIOException(e);
77+
}
78+
}
79+
80+
}
Binary file not shown.
Binary file not shown.

instant-server-ssl-reloading-with-vertx/vertx-server/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Open the certificate details in your browser by clicking on the lock logo (on Ch
3535
Please note down the expiration date. Afterwords you will compare it when you have run the admin application.
3636

3737
#### Refresh the server certificates
38-
Wait for one minute and if the keystores are found and have been updated, than the ssl configuration will be updated.
38+
Wait for one minute and if the keystores are found and have been updated, then the ssl configuration will be updated.
3939
Refresh your browser tab and open the certificate details again and compare the expiration date with the one you have noted down.
4040
You should have a similar certificate detail as shown below:
4141

pom.xml

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<module>instant-server-ssl-reloading-with-netty</module>
2222
<module>console-captor-examples</module>
2323
<module>instant-ssl-reloading-with-spring-jetty-database</module>
24+
<module>instant-server-ssl-reloading-with-quarkus</module>
2425
</modules>
2526

2627
<licenses>
@@ -76,6 +77,7 @@
7677
<version.websocket-client>9.4.50.v20221201</version.websocket-client>
7778
<version.vertx>4.3.7</version.vertx>
7879
<version.netty>4.1.86.Final</version.netty>
80+
<version.quarkus>3.0.3.Final</version.quarkus>
7981
<quarkus.platform.version>2.15.1.Final</quarkus.platform.version>
8082
<version.lombok>1.18.24</version.lombok>
8183
<version.jboss-logging>3.5.0.Final</version.jboss-logging>

0 commit comments

Comments
 (0)