From f0bf290c281d3826d4bc906808339be7fc2c9c0b Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Fri, 13 Sep 2024 03:47:21 -0400 Subject: [PATCH] fix: add a reload period property (#32715) closes: #23771 Signed-off-by: Steve Hawkins --- docs/documentation/release_notes/topics/26_0_0.adoc | 4 ++++ docs/guides/server/enabletls.adoc | 5 +++++ .../main/java/org/keycloak/config/HttpOptions.java | 6 ++++++ .../configuration/mappers/HttpPropertyMappers.java | 6 ++++++ .../configuration/mappers/PropertyMapper.java | 7 ++++++- .../configuration/test/ConfigurationTest.java | 11 +++++++++++ .../java/org/keycloak/it/cli/dist/HttpDistTest.java | 12 +++++++++++- ...HelpCommandDistTest.testStartDevHelp.approved.txt | 5 +++++ ...pCommandDistTest.testStartDevHelpAll.approved.txt | 5 +++++ .../HelpCommandDistTest.testStartHelp.approved.txt | 5 +++++ ...HelpCommandDistTest.testStartHelpAll.approved.txt | 5 +++++ ...mmandDistTest.testStartOptimizedHelp.approved.txt | 5 +++++ ...ndDistTest.testStartOptimizedHelpAll.approved.txt | 5 +++++ 13 files changed, 79 insertions(+), 2 deletions(-) diff --git a/docs/documentation/release_notes/topics/26_0_0.adoc b/docs/documentation/release_notes/topics/26_0_0.adoc index fc7fdd8c26..aff118a83e 100644 --- a/docs/documentation/release_notes/topics/26_0_0.adoc +++ b/docs/documentation/release_notes/topics/26_0_0.adoc @@ -142,6 +142,10 @@ The deprecated `proxy` option was removed. This option was deprecated in {projec The `proxy-trusted-addresses` can be used when the `proxy-headers` option is set to specify a allowlist of trusted proxy addresses. If the proxy address for a given request is not trusted, then the respective proxy header values will not be used. += Option to reload trust and key material added + +The `https-certificates-reload-period` option can be set to define the reloading period of key store, trust store, and certificate files referenced by https-* options. Use -1 to disable reloading. Defaults to 1h (one hour). + = Property `origin` in the `UserRepresentation` is deprecated The `origin` property in the `UserRepresentation` is deprecated and planned to be removed in future releases. diff --git a/docs/guides/server/enabletls.adoc b/docs/guides/server/enabletls.adoc index b98429b890..bd5e8ac9f2 100644 --- a/docs/guides/server/enabletls.adoc +++ b/docs/guides/server/enabletls.adoc @@ -85,4 +85,9 @@ NOTE: Management interface properties are inherited from the main HTTP server, i It means when mTLS is set, it is also enabled for the management interface. To override the behavior, use the `https-management-client-auth` property. +== Certificate and Key Reloading + +By default {project_name} will reload the certificates, keys, and keystores specified in `https-*` options every hour. For environments where your server keys may need frequent rotation, this allows that to happen without a server restart. You may override the default via the `https-certificates-reload-period` option. Interval on which to reload key store, trust store, and certificate files referenced by https-* options. +The value may be a java.time.Duration value, an integer number of seconds, or an integer followed by one of the time units [ms, h, m, s, d]. Must be greater than 30 seconds. Use -1 to disable. + diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/HttpOptions.java b/quarkus/config-api/src/main/java/org/keycloak/config/HttpOptions.java index f8d8883342..2036c509ce 100644 --- a/quarkus/config-api/src/main/java/org/keycloak/config/HttpOptions.java +++ b/quarkus/config-api/src/main/java/org/keycloak/config/HttpOptions.java @@ -63,6 +63,12 @@ public class HttpOptions { .defaultValue(Arrays.asList("TLSv1.3,TLSv1.2")) .build(); + public static final Option HTTPS_CERTIFICATES_RELOAD_PERIOD = new OptionBuilder<>("https-certificates-reload-period", String.class) + .category(OptionCategory.HTTP) + .description("Interval on which to reload key store, trust store, and certificate files referenced by https-* options. May be a java.time.Duration value, an integer number of seconds, or an integer followed by one of [ms, h, m, s, d]. Must be greater than 30 seconds. Use -1 to disable.") + .defaultValue("1h") + .build(); + public static final Option HTTPS_CERTIFICATE_FILE = new OptionBuilder<>("https-certificate-file", File.class) .category(OptionCategory.HTTP) .description("The file path to a server certificate or certificate chain in PEM format.") diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java index 439f90d376..01f22c58a8 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java @@ -68,6 +68,12 @@ public final class HttpPropertyMappers { .to("quarkus.http.ssl.protocols") .paramLabel("protocols") .build(), + fromOption(HttpOptions.HTTPS_CERTIFICATES_RELOAD_PERIOD) + .to("quarkus.http.ssl.certificate.reload-period") + // -1 means no reload + .transformer((value, context) -> "-1".equals(value.get()) ? null : value) + .paramLabel("reload period") + .build(), fromOption(HttpOptions.HTTPS_CERTIFICATE_FILE) .to(QUARKUS_HTTPS_CERT_FILES) .transformer(HttpPropertyMappers.validatePath(QUARKUS_HTTPS_CERT_FILES)) diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java index 932f62adaf..a21152242b 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java @@ -160,7 +160,12 @@ public class PropertyMapper { // we always fallback to the current value from the property we are mapping if (transformedValue == null) { - return context.proceed(name); + return ConfigValue.builder() + .withName(name) + .withValue(null) + .withRawValue(config.getValue()) + .withConfigSourceName(config.getConfigSourceName()) + .build(); } return transformedValue; diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java index 5fac5c23b1..230e951fd2 100644 --- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java +++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java @@ -18,6 +18,7 @@ package org.keycloak.quarkus.runtime.configuration.test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.keycloak.quarkus.runtime.Environment.isWindows; import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.CLI_ARGS; @@ -475,4 +476,14 @@ public class ConfigurationTest extends AbstractConfigurationTest { ConfigValue secret = config.getConfigValue("my.secret"); assertEquals("secret", secret.getValue()); } + + @Test + public void testReloadPeriod() { + ConfigArgsConfigSource.setCliArgs(""); + assertEquals("1h", createConfig().getConfigValue("quarkus.http.ssl.certificate.reload-period").getValue()); + ConfigArgsConfigSource.setCliArgs("--https-certificates-reload-period=-1"); + assertNull(createConfig().getConfigValue("quarkus.http.ssl.certificate.reload-period").getValue()); + ConfigArgsConfigSource.setCliArgs("--https-certificates-reload-period=2h"); + assertEquals("2h", createConfig().getConfigValue("quarkus.http.ssl.certificate.reload-period").getValue()); + } } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HttpDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HttpDistTest.java index ff675fa27f..dfbb056d22 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HttpDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HttpDistTest.java @@ -31,12 +31,16 @@ import java.util.concurrent.CompletableFuture; import static io.restassured.RestAssured.when; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; +import io.quarkus.test.junit.main.Launch; +import io.quarkus.test.junit.main.LaunchResult; + /** * @author Vaclav Muzikar */ -@DistributionTest(keepAlive = true) +@DistributionTest(keepAlive = true, enableTls = true) @RawDistOnly(reason = "Containers are immutable") public class HttpDistTest { @Test @@ -55,4 +59,10 @@ public class HttpDistTest { assertThat("Some of the requests should be properly rejected", statusCodes, hasItem(503)); assertThat("None of the requests should throw an unhandled exception", statusCodes, not(hasItem(500))); } + + @Test + @Launch({"start-dev", "--https-certificates-reload-period=wrong"}) + public void testHttpCertificateReloadPeriod(LaunchResult result) { + assertThat(result.getErrorOutput(), containsString("Text cannot be parsed to a Duration")); + } } diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.approved.txt index 0dd52d9047..d7fa40912c 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.approved.txt @@ -153,6 +153,11 @@ HTTP(S): The file path to a server certificate or certificate chain in PEM format. --https-certificate-key-file The file path to a private key in PEM format. +--https-certificates-reload-period + Interval on which to reload key store, trust store, and certificate files + referenced by https-* options. May be a java.time.Duration value, an integer + number of seconds, or an integer followed by one of [ms, h, m, s, d]. Must + be greater than 30 seconds. Use -1 to disable. Default: 1h. --https-cipher-suites The cipher suites to use. If none is given, a reasonable default is selected. --https-client-auth diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.approved.txt index f8ddb55062..6e98a7de27 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.approved.txt @@ -188,6 +188,11 @@ HTTP(S): The file path to a server certificate or certificate chain in PEM format. --https-certificate-key-file The file path to a private key in PEM format. +--https-certificates-reload-period + Interval on which to reload key store, trust store, and certificate files + referenced by https-* options. May be a java.time.Duration value, an integer + number of seconds, or an integer followed by one of [ms, h, m, s, d]. Must + be greater than 30 seconds. Use -1 to disable. Default: 1h. --https-cipher-suites The cipher suites to use. If none is given, a reasonable default is selected. --https-client-auth diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.approved.txt index f1412fafc3..954e02d214 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.approved.txt @@ -154,6 +154,11 @@ HTTP(S): The file path to a server certificate or certificate chain in PEM format. --https-certificate-key-file The file path to a private key in PEM format. +--https-certificates-reload-period + Interval on which to reload key store, trust store, and certificate files + referenced by https-* options. May be a java.time.Duration value, an integer + number of seconds, or an integer followed by one of [ms, h, m, s, d]. Must + be greater than 30 seconds. Use -1 to disable. Default: 1h. --https-cipher-suites The cipher suites to use. If none is given, a reasonable default is selected. --https-client-auth diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.approved.txt index f5268bec6e..6aa18d8d9c 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.approved.txt @@ -189,6 +189,11 @@ HTTP(S): The file path to a server certificate or certificate chain in PEM format. --https-certificate-key-file The file path to a private key in PEM format. +--https-certificates-reload-period + Interval on which to reload key store, trust store, and certificate files + referenced by https-* options. May be a java.time.Duration value, an integer + number of seconds, or an integer followed by one of [ms, h, m, s, d]. Must + be greater than 30 seconds. Use -1 to disable. Default: 1h. --https-cipher-suites The cipher suites to use. If none is given, a reasonable default is selected. --https-client-auth diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.approved.txt index 3c5fab1532..37c8c0a45b 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.approved.txt @@ -136,6 +136,11 @@ HTTP(S): The file path to a server certificate or certificate chain in PEM format. --https-certificate-key-file The file path to a private key in PEM format. +--https-certificates-reload-period + Interval on which to reload key store, trust store, and certificate files + referenced by https-* options. May be a java.time.Duration value, an integer + number of seconds, or an integer followed by one of [ms, h, m, s, d]. Must + be greater than 30 seconds. Use -1 to disable. Default: 1h. --https-cipher-suites The cipher suites to use. If none is given, a reasonable default is selected. --https-key-store-file diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.approved.txt index a0c836c371..ce1126134e 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.approved.txt @@ -171,6 +171,11 @@ HTTP(S): The file path to a server certificate or certificate chain in PEM format. --https-certificate-key-file The file path to a private key in PEM format. +--https-certificates-reload-period + Interval on which to reload key store, trust store, and certificate files + referenced by https-* options. May be a java.time.Duration value, an integer + number of seconds, or an integer followed by one of [ms, h, m, s, d]. Must + be greater than 30 seconds. Use -1 to disable. Default: 1h. --https-cipher-suites The cipher suites to use. If none is given, a reasonable default is selected. --https-key-store-file