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 4641000d54..09451ee3dd 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 @@ -59,12 +59,12 @@ public class HttpOptions { .defaultValue("TLSv1.3,TLSv1.2") .build(); - public static final Option HTTPS_CERTIFICATE_FILE = new OptionBuilder<>("https-certificate-file", File.class) + 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.") .build(); - public static final Option HTTPS_CERTIFICATE_KEY_FILE = new OptionBuilder<>("https-certificate-key-file", File.class) + public static final Option HTTPS_CERTIFICATE_KEY_FILE = new OptionBuilder<>("https-certificate-key-file", File.class) .category(OptionCategory.HTTP) .description("The file path to a private key in PEM format.") .build(); 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 ee7914f794..c1cb23be1a 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 @@ -13,6 +13,7 @@ import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider; import java.io.File; import java.nio.file.Paths; import java.util.Optional; +import java.util.function.BiFunction; import static java.util.Optional.empty; import static java.util.Optional.of; @@ -21,6 +22,8 @@ import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers import static org.keycloak.quarkus.runtime.integration.QuarkusPlatform.addInitializationException; final class HttpPropertyMappers { + private static final String QUARKUS_HTTPS_CERT_FILES = "quarkus.http.ssl.certificate.files"; + private static final String QUARKUS_HTTPS_CERT_KEY_FILES = "quarkus.http.ssl.certificate.key-files"; private HttpPropertyMappers(){} @@ -64,15 +67,17 @@ final class HttpPropertyMappers { .paramLabel("protocols") .build(), fromOption(HttpOptions.HTTPS_CERTIFICATE_FILE) - .to("quarkus.http.ssl.certificate.files") + .to(QUARKUS_HTTPS_CERT_FILES) + .transformer(HttpPropertyMappers.validatePath(QUARKUS_HTTPS_CERT_FILES)) .paramLabel("file") .build(), fromOption(HttpOptions.HTTPS_CERTIFICATE_KEY_FILE) - .to("quarkus.http.ssl.certificate.key-files") + .to(QUARKUS_HTTPS_CERT_KEY_FILES) + .transformer(HttpPropertyMappers.validatePath(QUARKUS_HTTPS_CERT_KEY_FILES)) .paramLabel("file") .build(), fromOption(HttpOptions.HTTPS_KEY_STORE_FILE - .withRuntimeSpecificDefault(getDefaultKeystorePathValue())) + .withRuntimeSpecificDefault(getDefaultKeystorePathValue())) .to("quarkus.http.ssl.certificate.key-store-file") .paramLabel("file") .build(), @@ -109,6 +114,10 @@ final class HttpPropertyMappers { }; } + private static BiFunction, ConfigSourceInterceptorContext, Optional> validatePath(String key) { + return (value, context) -> Environment.isWindows() ? value.filter(v -> v.equals(context.proceed(key).getValue())).map(p -> p.replace("\\", "/")) : value; + } + private static Optional getHttpEnabledTransformer(Optional value, ConfigSourceInterceptorContext context) { boolean enabled = Boolean.parseBoolean(value.get()); ConfigValue proxy = context.proceed(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + "proxy"); diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java index af2a5c2ace..e94f7ec782 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java @@ -28,6 +28,8 @@ import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; import org.keycloak.it.junit5.extension.BeforeStartDistribution; import org.keycloak.it.junit5.extension.CLIResult; import org.keycloak.it.junit5.extension.DistributionTest; @@ -105,7 +107,7 @@ public class QuarkusPropertiesDistTest { @Test @BeforeStartDistribution(UpdateHibernateMetricsFromQuarkusProps.class) @Launch({ "build", "--metrics-enabled=true" }) - @Order(8) + @Order(7) void buildFirstWithUnknownQuarkusBuildProperty(LaunchResult result) { CLIResult cliResult = (CLIResult) result; cliResult.assertBuild(); @@ -114,7 +116,7 @@ public class QuarkusPropertiesDistTest { @Test @KeepServerAlive @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", OPTIMIZED_BUILD_OPTION_LONG}) - @Order(9) + @Order(8) void testUnknownQuarkusBuildTimePropertyApplied(LaunchResult result) { CLIResult cliResult = (CLIResult) result; cliResult.assertNoBuild(); @@ -124,7 +126,7 @@ public class QuarkusPropertiesDistTest { @Test @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--config-keystore=../../../../src/test/resources/keystore" }) - @Order(10) + @Order(9) void testMissingSmallRyeKeyStorePasswordProperty(LaunchResult result) { CLIResult cliResult = (CLIResult) result; cliResult.assertError("config-keystore-password must be specified"); @@ -133,7 +135,7 @@ public class QuarkusPropertiesDistTest { @Test @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--config-keystore-password=secret" }) - @Order(11) + @Order(10) void testMissingSmallRyeKeyStorePathProperty(LaunchResult result) { CLIResult cliResult = (CLIResult) result; cliResult.assertError("config-keystore must be specified"); @@ -143,7 +145,7 @@ public class QuarkusPropertiesDistTest { @Test @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--config-keystore=/invalid/path", "--config-keystore-password=secret" }) - @Order(12) + @Order(11) void testInvalidSmallRyeKeyStorePathProperty(LaunchResult result) { CLIResult cliResult = (CLIResult) result; cliResult.assertError("java.lang.IllegalArgumentException: config-keystore path does not exist: /invalid/path"); @@ -153,7 +155,7 @@ public class QuarkusPropertiesDistTest { @Test @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--config-keystore=../../../../src/test/resources/keystore", "--config-keystore-password=secret" }) - @Order(13) + @Order(12) void testSmallRyeKeyStoreConfigSource(LaunchResult result) { // keytool -importpass -alias kc.log-level -keystore keystore -storepass secret -storetype PKCS12 -v (with "debug" as the stored password) CLIResult cliResult = (CLIResult) result; @@ -161,6 +163,30 @@ public class QuarkusPropertiesDistTest { cliResult.assertBuild(); } + @Test + @BeforeStartDistribution(ForceRebuild.class) + @DisabledOnOs(value = { OS.WINDOWS }, disabledReason = "Windows uses a different path separator.") + @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", + "--https-certificate-file=/tmp/kc/bin/../conf/server.crt.pem", + "--https-certificate-key-file=/tmp/kc/bin/../conf/server.key.pem" }) + @Order(13) + void testHttpCertsPathTransformer(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + assertTrue(cliResult.getOutput().contains("ERROR: /tmp/kc/bin/../conf/server.crt.pem")); + } + + @Test + @BeforeStartDistribution(ForceRebuild.class) + @DisabledOnOs(value = { OS.LINUX, OS.MAC }, disabledReason = "Windows uses a different path separator.") + @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", + "--https-certificate-file=C:\\tmp\\kc\\bin\\..\\conf/server.crt.pem", + "--https-certificate-key-file=C:\\tmp\\kc\\bin\\..\\conf/server.key.pem" }) + @Order(14) + void testHttpCertsPathTransformerOnWindows(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + assertTrue(cliResult.getOutput().contains("ERROR: C:/tmp/kc/bin/../conf/server.crt.pem")); + } + public static class UpdateConsoleLogLevelToWarnFromQuarkusProps implements Consumer { @Override public void accept(KeycloakDistribution distribution) { @@ -194,4 +220,13 @@ public class QuarkusPropertiesDistTest { distribution.setQuarkusProperty(QUARKUS_BUILDTIME_HIBERNATE_METRICS_KEY, "true"); } } + + public static class ForceRebuild implements Consumer { + + @Override + public void accept(KeycloakDistribution distribution) { + CLIResult buildResult = distribution.run("build"); + buildResult.assertBuild(); + } + } } \ No newline at end of file