From af1a5ea2a8a5a9f97591ac86d4f1fd19c40af729 Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Tue, 22 Oct 2024 13:05:56 -0400 Subject: [PATCH] fix: refining https file type detection (#33703) also making common trustore logic align closes: #33649 Signed-off-by: Steve Hawkins --- .../keycloak/common/util/KeystoreUtil.java | 23 ++-- .../common/util/KeystoreUtilTest.java | 14 ++- .../operator/advanced-configuration.adoc | 2 +- docs/guides/server/enabletls.adoc | 10 +- docs/guides/server/keycloak-truststore.adoc | 2 +- docs/guides/server/mutual-tls.adoc | 8 ++ .../keycloak/config/TruststoreOptions.java | 2 +- .../cli/ExecutionExceptionHandler.java | 3 + .../mappers/HttpPropertyMappers.java | 111 +++++++++--------- .../configuration/mappers/PropertyMapper.java | 2 + .../quarkus/runtime/cli/PicocliTest.java | 15 +++ .../configuration/test/ConfigurationTest.java | 13 ++ .../cli/dist/QuarkusPropertiesDistTest.java | 6 +- ...ommandDistTest.testExportHelp.approved.txt | 4 +- ...andDistTest.testExportHelpAll.approved.txt | 4 +- ...ommandDistTest.testImportHelp.approved.txt | 4 +- ...andDistTest.testImportHelpAll.approved.txt | 4 +- ...mandDistTest.testStartDevHelp.approved.txt | 4 +- ...dDistTest.testStartDevHelpAll.approved.txt | 4 +- ...CommandDistTest.testStartHelp.approved.txt | 4 +- ...mandDistTest.testStartHelpAll.approved.txt | 4 +- ...stTest.testStartOptimizedHelp.approved.txt | 4 +- ...est.testStartOptimizedHelpAll.approved.txt | 4 +- .../truststore/TruststoreBuilder.java | 10 +- 24 files changed, 164 insertions(+), 97 deletions(-) diff --git a/common/src/main/java/org/keycloak/common/util/KeystoreUtil.java b/common/src/main/java/org/keycloak/common/util/KeystoreUtil.java index 97645052ad..167d621ff5 100755 --- a/common/src/main/java/org/keycloak/common/util/KeystoreUtil.java +++ b/common/src/main/java/org/keycloak/common/util/KeystoreUtil.java @@ -39,7 +39,7 @@ public class KeystoreUtil { public enum KeystoreFormat { JKS("jks"), - PKCS12("p12", "pfx"), + PKCS12("p12", "pfx", "pkcs12"), BCFKS("bcfks"); // Typical file extension for this keystore format @@ -105,6 +105,17 @@ public class KeystoreUtil { throw new RuntimeException("Failed to load private key: " + e.getMessage(), e); } } + + public static Optional getKeystoreFormat(String path) { + int lastDotIndex = path.lastIndexOf('.'); + if (lastDotIndex > -1) { + String ext = path.substring(lastDotIndex + 1).toLowerCase(); + return Arrays.stream(KeystoreUtil.KeystoreFormat.values()) + .filter(ksFormat -> ksFormat.getFileExtensions().contains(ext)) + .findFirst(); + } + return Optional.empty(); + } /** @@ -120,13 +131,9 @@ public class KeystoreUtil { if (preferredType != null) return preferredType; // Fallback to path - int lastDotIndex = path.lastIndexOf('.'); - if (lastDotIndex > -1) { - String ext = path.substring(lastDotIndex + 1).toLowerCase(); - Optional detectedType = Arrays.stream(KeystoreUtil.KeystoreFormat.values()) - .filter(ksFormat -> ksFormat.getFileExtensions().contains(ext)) - .findFirst(); - if (detectedType.isPresent()) return detectedType.get().toString(); + Optional format = getKeystoreFormat(path); + if (format.isPresent()) { + return format.get().toString(); } // Fallback to default diff --git a/common/src/test/java/org/keycloak/common/util/KeystoreUtilTest.java b/common/src/test/java/org/keycloak/common/util/KeystoreUtilTest.java index 08828a5dfb..2a35fd5e3d 100644 --- a/common/src/test/java/org/keycloak/common/util/KeystoreUtilTest.java +++ b/common/src/test/java/org/keycloak/common/util/KeystoreUtilTest.java @@ -1,8 +1,9 @@ package org.keycloak.common.util; -import org.junit.Test; - import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import org.junit.Test; public class KeystoreUtilTest { @@ -11,6 +12,15 @@ public class KeystoreUtilTest { assertEquals("x", KeystoreUtil.getKeystoreType("x", "y", "z")); assertEquals("z", KeystoreUtil.getKeystoreType(null, "y", "z")); assertEquals(KeystoreUtil.KeystoreFormat.PKCS12.name(), KeystoreUtil.getKeystoreType(null, "y.pfx", "z")); + assertEquals(KeystoreUtil.KeystoreFormat.PKCS12.name(), KeystoreUtil.getKeystoreType(null, "y.pkcs12", "z")); + } + + @Test + public void testGetFormat() { + assertFalse(KeystoreUtil.getKeystoreFormat("some.file").isPresent()); + assertFalse(KeystoreUtil.getKeystoreFormat("somepfx").isPresent()); + assertEquals(KeystoreUtil.KeystoreFormat.PKCS12, KeystoreUtil.getKeystoreFormat("file.pfx").get()); + assertEquals(KeystoreUtil.KeystoreFormat.JKS, KeystoreUtil.getKeystoreFormat("file.jks").get()); } } \ No newline at end of file diff --git a/docs/guides/operator/advanced-configuration.adoc b/docs/guides/operator/advanced-configuration.adoc index a5bd69f37e..7ecf76ad42 100644 --- a/docs/guides/operator/advanced-configuration.adoc +++ b/docs/guides/operator/advanced-configuration.adoc @@ -275,7 +275,7 @@ To ensure proper TLS configuration, use the `tlsSecret` and `truststores` fields If you need to provide trusted certificates, the Keycloak CR provides a top level feature for configuring the server's truststore as discussed in <@links.server id="keycloak-truststore"/>. -Use the truststores stanza of the Keycloak spec to specify Secrets containing PEM encoded files, or PKCS12 files with extension `.p12` or `.pfx`, for example: +Use the truststores stanza of the Keycloak spec to specify Secrets containing PEM encoded files, or PKCS12 files with extension `.p12`, `.pfx`, or `.pkcs12`, for example: [source,yaml] ---- diff --git a/docs/guides/server/enabletls.adoc b/docs/guides/server/enabletls.adoc index a4d9841ca7..3286eda3b6 100644 --- a/docs/guides/server/enabletls.adoc +++ b/docs/guides/server/enabletls.adoc @@ -22,12 +22,20 @@ When you use a pair of matching certificate and private key files in PEM format, {project_name} creates a keystore out of these files in memory and uses this keystore afterwards. -== Providing a Java Keystore +== Providing a Keystore When no keystore file is explicitly configured, but `http-enabled` is set to false, {project_name} looks for a `conf/server.keystore` file. As an alternative, you can use an existing keystore by running the following command: <@kc.start parameters="--https-key-store-file=/path/to/existing-keystore-file"/> +Recognized file extensions for a keystore: + +* `.p12`, `.pkcs12`, and `.pfx` for a pkcs12 file +* `.jks`, and `.keystore` for a jks file +* `.key`, `.crt`, and `.pem` for a pem file + +If your keystore does not have an extension matching its file type, you will also need to set the `https-key-store-type` option. + === Setting the Keystore password You can set a secure password for your keystore using the `https-key-store-password` option: <@kc.start parameters="--https-key-store-password="/> diff --git a/docs/guides/server/keycloak-truststore.adoc b/docs/guides/server/keycloak-truststore.adoc index dfd3f6e7bf..2064a3eaaa 100644 --- a/docs/guides/server/keycloak-truststore.adoc +++ b/docs/guides/server/keycloak-truststore.adoc @@ -12,7 +12,7 @@ The certificates of these clients or servers, or the CA that signed these certif == Configuring the System Truststore -The existing Java default truststore certs will always be trusted. If you need additional certificates, which will be the case if you have self-signed or internal certificate authorities that are not recognized by the JRE, they can be included in the `conf/truststores` directory or subdirectories. The certs may be in PEM files, or PKCS12 files with extension `.p12` or `.pfx`. If in PKCS12, the certs must be unencrypted - meaning no password is expected. +The existing Java default truststore certs will always be trusted. If you need additional certificates, which will be the case if you have self-signed or internal certificate authorities that are not recognized by the JRE, they can be included in the `conf/truststores` directory or subdirectories. The certs may be in PEM files, or PKCS12 files with extension `.p12`, `.pfx`, or `.pkcs12`. If in PKCS12, the certs must be unencrypted - meaning no password is expected. If you need an alternative path, use the `--truststore-paths` option to specify additional files or directories where PEM or PKCS12 files are located. Paths are relative to where you launched {project_name}, so absolute paths are recommended instead. If a directory is specified, it will be recursively scanned for truststore files. diff --git a/docs/guides/server/mutual-tls.adoc b/docs/guides/server/mutual-tls.adoc index 9eada0d0a2..933e6011ee 100644 --- a/docs/guides/server/mutual-tls.adoc +++ b/docs/guides/server/mutual-tls.adoc @@ -35,6 +35,14 @@ By default, {project_name} uses the System Truststore to validate certificates. If you need to use a dedicated truststore for mTLS, you can configure the location of this truststore by running the following command: <@kc.start parameters="--https-trust-store-file=/path/to/file --https-trust-store-password="/> +Recognized file extensions for a truststore: + +* `.p12`, `.pkcs12`, and `.pfx` for a pkcs12 file +* `.jks`, and `.truststore` for a jks file +* `.ca`, `.crt`, and `.pem` for a pem file + +If your truststore does not have an extension matching its file type, you will also need to set the `https-key-store-type` option. + == Additional resources === Using mTLS for outgoing HTTP requests diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/TruststoreOptions.java b/quarkus/config-api/src/main/java/org/keycloak/config/TruststoreOptions.java index 73f82bc9e5..e419e34ffa 100644 --- a/quarkus/config-api/src/main/java/org/keycloak/config/TruststoreOptions.java +++ b/quarkus/config-api/src/main/java/org/keycloak/config/TruststoreOptions.java @@ -9,7 +9,7 @@ public class TruststoreOptions { public static final Option> TRUSTSTORE_PATHS = OptionBuilder.listOptionBuilder("truststore-paths", String.class) .category(OptionCategory.TRUSTSTORE) - .description("List of pkcs12 (p12 or pfx file extensions), PEM files, or directories containing those files that will be used as a system truststore.") + .description("List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or directories containing those files that will be used as a system truststore.") .build(); public static final Option HOSTNAME_VERIFICATION_POLICY = new OptionBuilder<>("tls-hostname-verifier", HostnameVerificationPolicy.class) diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ExecutionExceptionHandler.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ExecutionExceptionHandler.java index b2875affc1..55455d7b72 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ExecutionExceptionHandler.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ExecutionExceptionHandler.java @@ -44,6 +44,9 @@ public final class ExecutionExceptionHandler implements CommandLine.IExecutionEx if (cause instanceof PropertyException) { PrintWriter writer = cmd.getErr(); writer.println(cmd.getColorScheme().errorText(cause.getMessage())); + if (verbose && cause.getCause() != null) { + dumpException(writer, cause.getCause()); + } return ShortErrorMessageHandler.getInvalidInputExitCode(cause, cmd); } error(cmd.getErr(), "Failed to run '" + parseResult.subcommands().stream() 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 1e2c1503fb..63a65634bd 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 @@ -1,5 +1,6 @@ package org.keycloak.quarkus.runtime.configuration.mappers; +import io.quarkus.runtime.util.ClassPathUtils; import io.quarkus.vertx.http.runtime.CertificateConfig; import io.quarkus.vertx.http.runtime.options.TlsUtils; import io.smallrye.config.ConfigSourceInterceptorContext; @@ -14,9 +15,10 @@ import org.keycloak.quarkus.runtime.configuration.Configuration; import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; import java.util.Optional; -import java.util.function.BiFunction; import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption; @@ -24,6 +26,10 @@ public final class HttpPropertyMappers { private static final int MIN_MAX_THREADS = 50; 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 static final String QUARKUS_HTTPS_KEY_STORE_FILE = "quarkus.http.ssl.certificate.key-store-file"; + private static final String QUARKUS_HTTPS_TRUST_STORE_FILE = "quarkus.http.ssl.certificate.trust-store-file"; + private static final String QUARKUS_HTTPS_TRUST_STORE_FILE_TYPE = "quarkus.http.ssl.certificate.trust-store-file-type"; + private static final String QUARKUS_HTTPS_KEY_STORE_FILE_TYPE = "quarkus.http.ssl.certificate.key-store-file-type"; private HttpPropertyMappers(){} @@ -72,17 +78,18 @@ public final class HttpPropertyMappers { .build(), fromOption(HttpOptions.HTTPS_CERTIFICATE_FILE) .to(QUARKUS_HTTPS_CERT_FILES) - .transformer(HttpPropertyMappers.validatePath(QUARKUS_HTTPS_CERT_FILES)) + .transformer(HttpPropertyMappers::transformPath) .paramLabel("file") .build(), fromOption(HttpOptions.HTTPS_CERTIFICATE_KEY_FILE) .to(QUARKUS_HTTPS_CERT_KEY_FILES) - .transformer(HttpPropertyMappers.validatePath(QUARKUS_HTTPS_CERT_KEY_FILES)) + .transformer(HttpPropertyMappers::transformPath) .paramLabel("file") .build(), fromOption(HttpOptions.HTTPS_KEY_STORE_FILE .withRuntimeSpecificDefault(getDefaultKeystorePathValue())) - .to("quarkus.http.ssl.certificate.key-store-file") + .to(QUARKUS_HTTPS_KEY_STORE_FILE) + .transformer(HttpPropertyMappers::transformPath) .paramLabel("file") .build(), fromOption(HttpOptions.HTTPS_KEY_STORE_PASSWORD) @@ -91,12 +98,13 @@ public final class HttpPropertyMappers { .isMasked(true) .build(), fromOption(HttpOptions.HTTPS_KEY_STORE_TYPE) - .to("quarkus.http.ssl.certificate.key-store-file-type") .mapFrom(SecurityOptions.FIPS_MODE, HttpPropertyMappers::resolveKeyStoreType) + .to(QUARKUS_HTTPS_KEY_STORE_FILE_TYPE) .paramLabel("type") .build(), fromOption(HttpOptions.HTTPS_TRUST_STORE_FILE) - .to("quarkus.http.ssl.certificate.trust-store-file") + .to(QUARKUS_HTTPS_TRUST_STORE_FILE) + .transformer(HttpPropertyMappers::transformPath) .paramLabel("file") .build(), fromOption(HttpOptions.HTTPS_TRUST_STORE_PASSWORD) @@ -105,8 +113,9 @@ public final class HttpPropertyMappers { .isMasked(true) .build(), fromOption(HttpOptions.HTTPS_TRUST_STORE_TYPE) - .to("quarkus.http.ssl.certificate.trust-store-file-type") .mapFrom(SecurityOptions.FIPS_MODE, HttpPropertyMappers::resolveKeyStoreType) + .to(QUARKUS_HTTPS_TRUST_STORE_FILE_TYPE) + .transformer(HttpPropertyMappers::resolveKeyStoreType) .paramLabel("type") .build(), fromOption(HttpOptions.HTTP_MAX_QUEUED_REQUESTS) @@ -130,68 +139,60 @@ public final class HttpPropertyMappers { public static void validateConfig() { boolean enabled = isHttpEnabled(Configuration.getOptionalKcValue(HttpOptions.HTTP_ENABLED.getKey()).orElse(null)); - boolean trustStoreFile = Configuration.getOptionalKcValue(HttpOptions.HTTPS_TRUST_STORE_FILE.getKey()).isPresent(); - boolean keyStoreFile = Configuration.getOptionalKcValue(HttpOptions.HTTPS_KEY_STORE_FILE.getKey()).isPresent(); + Optional certFile = Configuration.getOptionalKcValue(HttpOptions.HTTPS_CERTIFICATE_FILE.getKey()); + Optional keystoreFile = Configuration.getOptionalKcValue(HttpOptions.HTTPS_KEY_STORE_FILE.getKey()); + if (!enabled && certFile.isEmpty() && keystoreFile.isEmpty()) { + throw new PropertyException(Messages.httpsConfigurationNotSet()); + } - if (trustStoreFile) { - CertificateConfig config = new CertificateConfig(); + CertificateConfig config = new CertificateConfig(); - config.trustStoreFile = Configuration.getOptionalKcValue(HttpOptions.HTTPS_TRUST_STORE_FILE.getKey()).map(Paths::get); - config.trustStorePassword = Configuration.getOptionalKcValue(HttpOptions.HTTPS_TRUST_STORE_PASSWORD.getKey()); - config.trustStoreFileType = Configuration.getOptionalKcValue(HttpOptions.HTTPS_TRUST_STORE_TYPE.getKey()); - config.trustStoreProvider = Configuration.getOptionalValue("quarkus.http.ssl.certificate.trust-store-provider"); - config.trustStoreCertAlias = Configuration.getOptionalValue("quarkus.http.ssl.certificate.trust-store-cert-alias"); - config.trustStoreFiles = Optional.empty(); + config.trustStoreFile = Configuration.getOptionalKcValue(HttpOptions.HTTPS_TRUST_STORE_FILE.getKey()).map(Paths::get); + config.trustStorePassword = Configuration.getOptionalKcValue(HttpOptions.HTTPS_TRUST_STORE_PASSWORD.getKey()); + config.trustStoreFileType = Configuration.getOptionalKcValue(HttpOptions.HTTPS_TRUST_STORE_TYPE.getKey()); + config.trustStoreProvider = Configuration.getOptionalValue("quarkus.http.ssl.certificate.trust-store-provider"); + config.trustStoreCertAlias = Configuration.getOptionalValue("quarkus.http.ssl.certificate.trust-store-cert-alias"); + config.trustStoreFiles = Optional.empty(); - try { - TlsUtils.computeTrustOptions(config, config.trustStorePassword); - } catch (IOException e) { - throw new PropertyException("Failed to load 'https-trust-store' material.", e); - } catch (IllegalArgumentException e) { + config.keyStoreFile = keystoreFile.map(Paths::get); + config.keyStorePassword = Configuration.getOptionalKcValue(HttpOptions.HTTPS_KEY_STORE_PASSWORD.getKey()); + config.keyStoreFileType = Configuration.getOptionalKcValue(HttpOptions.HTTPS_KEY_STORE_TYPE.getKey()); + config.keyStoreProvider = Configuration.getOptionalValue("quarkus.http.ssl.certificate.key-store-provider"); + config.keyStoreAlias = Configuration.getOptionalValue("quarkus.http.ssl.certificate.key-store-alias"); + config.keyStoreAliasPassword = Configuration.getOptionalValue("quarkus.http.ssl.certificate.key-store-alias-password"); + config.keyStoreAliasPasswordKey = Configuration.getOptionalValue("quarkus.http.ssl.certificate.key-store-alias-password-key"); + config.keyStoreKeyAlias = Configuration.getOptionalValue("quarkus.http.ssl.certificate.key-store-key-alias"); + + config.keyFiles = Configuration.getOptionalKcValue(HttpOptions.HTTPS_CERTIFICATE_KEY_FILE.getKey()).map(Paths::get).map(List::of); + config.files = certFile.map(Paths::get).map(List::of); + + try { + TlsUtils.computeTrustOptions(config, config.trustStorePassword); + } catch (IOException e) { + throw new PropertyException("Failed to load 'https-trust-store' material: " + e.getClass().getSimpleName() + " " + e.getMessage(), e); + } catch (IllegalArgumentException e) { + if (e.getMessage().contains(QUARKUS_HTTPS_TRUST_STORE_FILE_TYPE)) { throw new PropertyException("Unable to determine 'https-trust-store-type' automatically. " + "Adjust the file extension or specify the property.", e); } + throw new PropertyException(e.getMessage(), e); } - if (keyStoreFile) { - CertificateConfig config = new CertificateConfig(); - - config.keyStoreFile = Configuration.getOptionalKcValue(HttpOptions.HTTPS_KEY_STORE_FILE.getKey()).map(Paths::get); - config.keyStorePassword = Configuration.getOptionalKcValue(HttpOptions.HTTPS_KEY_STORE_PASSWORD.getKey()); - config.keyStoreFileType = Configuration.getOptionalKcValue(HttpOptions.HTTPS_KEY_STORE_TYPE.getKey()); - config.keyStoreProvider = Configuration.getOptionalValue("quarkus.http.ssl.certificate.key-store-provider"); - config.keyStoreAlias = Configuration.getOptionalValue("quarkus.http.ssl.certificate.key-store-alias"); - config.keyStoreAliasPassword = Configuration.getOptionalValue("quarkus.http.ssl.certificate.key-store-alias-password"); - config.keyStoreAliasPasswordKey = Configuration.getOptionalValue("quarkus.http.ssl.certificate.key-store-alias-password-key"); - config.keyStoreKeyAlias = Configuration.getOptionalValue("quarkus.http.ssl.certificate.key-store-key-alias"); - config.keyFiles = Optional.empty(); - config.files = Optional.empty(); - - try { - TlsUtils.computeKeyStoreOptions(config, config.keyStorePassword, config.keyStoreAliasPassword); - } catch (IOException e) { - throw new PropertyException("Failed to load 'https-key-store' material.", e); - } catch (IllegalArgumentException e) { + try { + TlsUtils.computeKeyStoreOptions(config, config.keyStorePassword, config.keyStoreAliasPassword); + } catch (IOException e) { + throw new PropertyException("Failed to load 'https-key-' material: " + e.getClass().getSimpleName() + " " + e.getMessage(), e); + } catch (IllegalArgumentException e) { + if (e.getMessage().contains(QUARKUS_HTTPS_KEY_STORE_FILE_TYPE)) { throw new PropertyException("Unable to determine 'https-key-store-type' automatically. " + "Adjust the file extension or specify the property.", e); } - } - - if (!enabled) { - Optional value = Configuration.getOptionalKcValue(HttpOptions.HTTPS_CERTIFICATE_FILE.getKey()); - - if (value.isEmpty()) { - value = Configuration.getOptionalValue("quarkus.http.ssl.certificate.key-store-file"); - } - - if (value.isEmpty()) { - throw new PropertyException(Messages.httpsConfigurationNotSet()); - } + throw new PropertyException(e.getMessage(), e); } } - private static BiFunction validatePath(String key) { - return (value, context) -> Environment.isWindows() && value != null && value.equals(context.proceed(key).getValue()) ? value.replace("\\", "/") : value; + private static String transformPath(String value, ConfigSourceInterceptorContext context) { + return value == null ? value : ClassPathUtils.toResourceName(Path.of(value)); } private static String getHttpEnabledTransformer(String value, ConfigSourceInterceptorContext context) { 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 18d809bd7a..7df87133a5 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 @@ -288,6 +288,8 @@ public class PropertyMapper { * NOTE: This transformer will not apply to the mapFrom value. When using * {@link #mapFrom} you generally need a transformer specifically for the parent * value, see {@link #mapFrom(Option, BiFunction)} + *

+ * The value passed into the transformer may be null if the property has no value set, and no default */ public Builder transformer(BiFunction mapper) { this.mapper = mapper; diff --git a/quarkus/runtime/src/test/java/or/keycloak/quarkus/runtime/cli/PicocliTest.java b/quarkus/runtime/src/test/java/or/keycloak/quarkus/runtime/cli/PicocliTest.java index 5897ef8404..15a20152a9 100644 --- a/quarkus/runtime/src/test/java/or/keycloak/quarkus/runtime/cli/PicocliTest.java +++ b/quarkus/runtime/src/test/java/or/keycloak/quarkus/runtime/cli/PicocliTest.java @@ -206,6 +206,21 @@ public class PicocliTest extends AbstractConfigurationTest { .errorText("Unknown option: '--db-pasword'") + "\nPossible solutions: --db-url, --db-url-host, --db-url-database, --db-url-port, --db-url-properties, --db-username, --db-password, --db-schema, --db-pool-initial-size, --db-pool-min-size, --db-pool-max-size, --db-driver, --db")); } + + @Test + public void httpStoreTypeValidation() { + NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--https-key-store-file=not-there.ks", "--hostname-strict=false"); + assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode); + assertThat(nonRunningPicocli.getErrString(), containsString("Unable to determine 'https-key-store-type' automatically. Adjust the file extension or specify the property")); + + nonRunningPicocli = pseudoLaunch("start", "--https-key-store-file=not-there.ks", "--hostname-strict=false", "--https-key-store-type=jdk"); + assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode); + assertThat(nonRunningPicocli.getErrString(), containsString("Failed to load 'https-key-' material: NoSuchFileException not-there.ks")); + + nonRunningPicocli = pseudoLaunch("start", "--https-trust-store-file=not-there.jks", "--https-key-store-file=not-there.ks", "--hostname-strict=false", "--https-key-store-type=jdk"); + assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode); + assertThat(nonRunningPicocli.getErrString(), containsString("No trust store password provided")); + } @Test public void failSingleParamWithSpace() { 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 028f72dc21..b47bb42f77 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 @@ -23,6 +23,8 @@ import static org.junit.Assert.assertTrue; import static org.keycloak.quarkus.runtime.Environment.isWindows; import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.CLI_ARGS; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.Arrays; import java.util.HashMap; @@ -491,7 +493,18 @@ public class ConfigurationTest extends AbstractConfigurationTest { ConfigArgsConfigSource.setCliArgs("--https-certificates-reload-period=2h"); assertEquals("2h", createConfig().getConfigValue("quarkus.http.ssl.certificate.reload-period").getValue()); } + + @Test + public void testHttpsPaths() { + ConfigArgsConfigSource.setCliArgs("--https-certificate-file=\\some\\file"); + String expected = "\\some\\file"; + if (FileSystems.getDefault().getSeparator().equals("\\")) { + expected = "/some/file"; + } + assertEquals(expected, createConfig().getConfigValue("quarkus.http.ssl.certificate.files").getValue()); + } + @Test public void testCacheMaxCount() { int maxCount = 500; 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 34ef5f12d3..70a2fda9c1 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 @@ -173,13 +173,13 @@ public class QuarkusPropertiesDistTest { @Test @BeforeStartDistribution(ForceRebuild.class) @DisabledOnOs(value = { OS.WINDOWS }, disabledReason = "Windows uses a different path separator.") - @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", + @Launch({ "start", "--verbose", "--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; - assertThat(cliResult.getOutput(),containsString("ERROR: /tmp/kc/bin/../conf/server.crt.pem")); + assertThat(cliResult.getErrorOutput(),containsString("Failed to load 'https-key-' material: NoSuchFileException /tmp/kc/bin/../conf/server.crt.pem")); } @Test @@ -191,7 +191,7 @@ public class QuarkusPropertiesDistTest { @Order(14) void testHttpCertsPathTransformerOnWindows(LaunchResult result) { CLIResult cliResult = (CLIResult) result; - assertThat(cliResult.getOutput(),containsString("ERROR: C:/tmp/kc/bin/../conf/server.crt.pem")); + assertThat(cliResult.getErrorOutput(),containsString("Failed to load 'https-key-' material: NoSuchFileException C:/tmp/kc/bin/../conf/server.crt.pem")); } public static class AddConsoleHandlerFromQuarkusProps implements Consumer { diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.approved.txt index 00d72240a5..f55aff04d4 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.approved.txt @@ -150,8 +150,8 @@ Truststore: Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated), DEFAULT. Default: DEFAULT. --truststore-paths - List of pkcs12 (p12 or pfx file extensions), PEM files, or directories - containing those files that will be used as a system truststore. + List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or + directories containing those files that will be used as a system truststore. Export: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.approved.txt index 7294a04863..092dce4e76 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.approved.txt @@ -248,8 +248,8 @@ Truststore: Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated), DEFAULT. Default: DEFAULT. --truststore-paths - List of pkcs12 (p12 or pfx file extensions), PEM files, or directories - containing those files that will be used as a system truststore. + List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or + directories containing those files that will be used as a system truststore. Export: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.approved.txt index 873ab40cab..f59c99c80b 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.approved.txt @@ -150,8 +150,8 @@ Truststore: Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated), DEFAULT. Default: DEFAULT. --truststore-paths - List of pkcs12 (p12 or pfx file extensions), PEM files, or directories - containing those files that will be used as a system truststore. + List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or + directories containing those files that will be used as a system truststore. Import: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.approved.txt index a933e339b3..fd30554ce7 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.approved.txt @@ -248,8 +248,8 @@ Truststore: Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated), DEFAULT. Default: DEFAULT. --truststore-paths - List of pkcs12 (p12 or pfx file extensions), PEM files, or directories - containing those files that will be used as a system truststore. + List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or + directories containing those files that will be used as a system truststore. Import: 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 14734a3f87..e8cfac27bf 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 @@ -321,8 +321,8 @@ Truststore: Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated), DEFAULT. Default: DEFAULT. --truststore-paths - List of pkcs12 (p12 or pfx file extensions), PEM files, or directories - containing those files that will be used as a system truststore. + List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or + directories containing those files that will be used as a system truststore. Security: 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 f62129cbc5..0a3b45389e 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 @@ -454,8 +454,8 @@ Truststore: Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated), DEFAULT. Default: DEFAULT. --truststore-paths - List of pkcs12 (p12 or pfx file extensions), PEM files, or directories - containing those files that will be used as a system truststore. + List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or + directories containing those files that will be used as a system truststore. Security: 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 03aa2f3449..205446df9e 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 @@ -322,8 +322,8 @@ Truststore: Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated), DEFAULT. Default: DEFAULT. --truststore-paths - List of pkcs12 (p12 or pfx file extensions), PEM files, or directories - containing those files that will be used as a system truststore. + List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or + directories containing those files that will be used as a system truststore. Security: 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 8512d1d7f1..e7fc914f06 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 @@ -455,8 +455,8 @@ Truststore: Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated), DEFAULT. Default: DEFAULT. --truststore-paths - List of pkcs12 (p12 or pfx file extensions), PEM files, or directories - containing those files that will be used as a system truststore. + List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or + directories containing those files that will be used as a system truststore. Security: 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 e0b7d261a7..e8e710d035 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 @@ -273,8 +273,8 @@ Truststore: Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated), DEFAULT. Default: DEFAULT. --truststore-paths - List of pkcs12 (p12 or pfx file extensions), PEM files, or directories - containing those files that will be used as a system truststore. + List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or + directories containing those files that will be used as a system truststore. Bootstrap Admin: 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 08645b9f7f..3032205680 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 @@ -395,8 +395,8 @@ Truststore: Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated), DEFAULT. Default: DEFAULT. --truststore-paths - List of pkcs12 (p12 or pfx file extensions), PEM files, or directories - containing those files that will be used as a system truststore. + List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or + directories containing those files that will be used as a system truststore. Bootstrap Admin: diff --git a/services/src/main/java/org/keycloak/truststore/TruststoreBuilder.java b/services/src/main/java/org/keycloak/truststore/TruststoreBuilder.java index c5d7a0e8ad..af4e011df0 100644 --- a/services/src/main/java/org/keycloak/truststore/TruststoreBuilder.java +++ b/services/src/main/java/org/keycloak/truststore/TruststoreBuilder.java @@ -19,6 +19,7 @@ package org.keycloak.truststore; import org.jboss.logging.Logger; import org.keycloak.common.util.KeystoreUtil; +import org.keycloak.common.util.KeystoreUtil.KeystoreFormat; import java.io.File; import java.io.FileInputStream; @@ -101,15 +102,14 @@ public class TruststoreBuilder { if (f.isDirectory()) { mergeFiles(Stream.of(f.listFiles()).map(File::getAbsolutePath).toArray(String[]::new), truststore, false, discoveredFiles); } else { - if (file.endsWith(".p12") || file.endsWith(".pfx")) { + var format = KeystoreUtil.getKeystoreFormat(file).orElse(null); + if (format == KeystoreFormat.PKCS12) { mergeTrustStore(truststore, file, loadStore(file, PKCS12, null)); if (!topLevel) { discoveredFiles.add(f.getAbsolutePath()); } - } else { - if (mergePemFile(truststore, file, topLevel) && !topLevel) { - discoveredFiles.add(f.getAbsolutePath()); - } + } else if (mergePemFile(truststore, file, topLevel) && !topLevel) { + discoveredFiles.add(f.getAbsolutePath()); } } }