fix: refining https file type detection (#33703)
also making common trustore logic align closes: #33649 Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
parent
e3936a9b38
commit
af1a5ea2a8
24 changed files with 164 additions and 97 deletions
|
@ -39,7 +39,7 @@ public class KeystoreUtil {
|
||||||
|
|
||||||
public enum KeystoreFormat {
|
public enum KeystoreFormat {
|
||||||
JKS("jks"),
|
JKS("jks"),
|
||||||
PKCS12("p12", "pfx"),
|
PKCS12("p12", "pfx", "pkcs12"),
|
||||||
BCFKS("bcfks");
|
BCFKS("bcfks");
|
||||||
|
|
||||||
// Typical file extension for this keystore format
|
// Typical file extension for this keystore format
|
||||||
|
@ -106,6 +106,17 @@ public class KeystoreUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Optional<KeystoreFormat> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to return supported keystore type
|
* Try to return supported keystore type
|
||||||
|
@ -120,13 +131,9 @@ public class KeystoreUtil {
|
||||||
if (preferredType != null) return preferredType;
|
if (preferredType != null) return preferredType;
|
||||||
|
|
||||||
// Fallback to path
|
// Fallback to path
|
||||||
int lastDotIndex = path.lastIndexOf('.');
|
Optional<KeystoreFormat> format = getKeystoreFormat(path);
|
||||||
if (lastDotIndex > -1) {
|
if (format.isPresent()) {
|
||||||
String ext = path.substring(lastDotIndex + 1).toLowerCase();
|
return format.get().toString();
|
||||||
Optional<KeystoreFormat> detectedType = Arrays.stream(KeystoreUtil.KeystoreFormat.values())
|
|
||||||
.filter(ksFormat -> ksFormat.getFileExtensions().contains(ext))
|
|
||||||
.findFirst();
|
|
||||||
if (detectedType.isPresent()) return detectedType.get().toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to default
|
// Fallback to default
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package org.keycloak.common.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
public class KeystoreUtilTest {
|
public class KeystoreUtilTest {
|
||||||
|
|
||||||
|
@ -11,6 +12,15 @@ public class KeystoreUtilTest {
|
||||||
assertEquals("x", KeystoreUtil.getKeystoreType("x", "y", "z"));
|
assertEquals("x", KeystoreUtil.getKeystoreType("x", "y", "z"));
|
||||||
assertEquals("z", KeystoreUtil.getKeystoreType(null, "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.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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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"/>.
|
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]
|
[source,yaml]
|
||||||
----
|
----
|
||||||
|
|
|
@ -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.
|
{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.
|
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:
|
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"/>
|
<@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
|
=== Setting the Keystore password
|
||||||
You can set a secure password for your keystore using the `https-key-store-password` option:
|
You can set a secure password for your keystore using the `https-key-store-password` option:
|
||||||
<@kc.start parameters="--https-key-store-password=<value>"/>
|
<@kc.start parameters="--https-key-store-password=<value>"/>
|
||||||
|
|
|
@ -12,7 +12,7 @@ The certificates of these clients or servers, or the CA that signed these certif
|
||||||
|
|
||||||
== Configuring the System Truststore
|
== 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.
|
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.
|
||||||
|
|
||||||
|
|
|
@ -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:
|
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=<value>"/>
|
<@kc.start parameters="--https-trust-store-file=/path/to/file --https-trust-store-password=<value>"/>
|
||||||
|
|
||||||
|
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
|
== Additional resources
|
||||||
|
|
||||||
=== Using mTLS for outgoing HTTP requests
|
=== Using mTLS for outgoing HTTP requests
|
||||||
|
|
|
@ -9,7 +9,7 @@ public class TruststoreOptions {
|
||||||
|
|
||||||
public static final Option<List<String>> TRUSTSTORE_PATHS = OptionBuilder.listOptionBuilder("truststore-paths", String.class)
|
public static final Option<List<String>> TRUSTSTORE_PATHS = OptionBuilder.listOptionBuilder("truststore-paths", String.class)
|
||||||
.category(OptionCategory.TRUSTSTORE)
|
.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();
|
.build();
|
||||||
|
|
||||||
public static final Option<HostnameVerificationPolicy> HOSTNAME_VERIFICATION_POLICY = new OptionBuilder<>("tls-hostname-verifier", HostnameVerificationPolicy.class)
|
public static final Option<HostnameVerificationPolicy> HOSTNAME_VERIFICATION_POLICY = new OptionBuilder<>("tls-hostname-verifier", HostnameVerificationPolicy.class)
|
||||||
|
|
|
@ -44,6 +44,9 @@ public final class ExecutionExceptionHandler implements CommandLine.IExecutionEx
|
||||||
if (cause instanceof PropertyException) {
|
if (cause instanceof PropertyException) {
|
||||||
PrintWriter writer = cmd.getErr();
|
PrintWriter writer = cmd.getErr();
|
||||||
writer.println(cmd.getColorScheme().errorText(cause.getMessage()));
|
writer.println(cmd.getColorScheme().errorText(cause.getMessage()));
|
||||||
|
if (verbose && cause.getCause() != null) {
|
||||||
|
dumpException(writer, cause.getCause());
|
||||||
|
}
|
||||||
return ShortErrorMessageHandler.getInvalidInputExitCode(cause, cmd);
|
return ShortErrorMessageHandler.getInvalidInputExitCode(cause, cmd);
|
||||||
}
|
}
|
||||||
error(cmd.getErr(), "Failed to run '" + parseResult.subcommands().stream()
|
error(cmd.getErr(), "Failed to run '" + parseResult.subcommands().stream()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.quarkus.runtime.configuration.mappers;
|
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.CertificateConfig;
|
||||||
import io.quarkus.vertx.http.runtime.options.TlsUtils;
|
import io.quarkus.vertx.http.runtime.options.TlsUtils;
|
||||||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||||
|
@ -14,9 +15,10 @@ import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.BiFunction;
|
|
||||||
|
|
||||||
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
|
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 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_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_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(){}
|
private HttpPropertyMappers(){}
|
||||||
|
|
||||||
|
@ -72,17 +78,18 @@ public final class HttpPropertyMappers {
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(HttpOptions.HTTPS_CERTIFICATE_FILE)
|
fromOption(HttpOptions.HTTPS_CERTIFICATE_FILE)
|
||||||
.to(QUARKUS_HTTPS_CERT_FILES)
|
.to(QUARKUS_HTTPS_CERT_FILES)
|
||||||
.transformer(HttpPropertyMappers.validatePath(QUARKUS_HTTPS_CERT_FILES))
|
.transformer(HttpPropertyMappers::transformPath)
|
||||||
.paramLabel("file")
|
.paramLabel("file")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(HttpOptions.HTTPS_CERTIFICATE_KEY_FILE)
|
fromOption(HttpOptions.HTTPS_CERTIFICATE_KEY_FILE)
|
||||||
.to(QUARKUS_HTTPS_CERT_KEY_FILES)
|
.to(QUARKUS_HTTPS_CERT_KEY_FILES)
|
||||||
.transformer(HttpPropertyMappers.validatePath(QUARKUS_HTTPS_CERT_KEY_FILES))
|
.transformer(HttpPropertyMappers::transformPath)
|
||||||
.paramLabel("file")
|
.paramLabel("file")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(HttpOptions.HTTPS_KEY_STORE_FILE
|
fromOption(HttpOptions.HTTPS_KEY_STORE_FILE
|
||||||
.withRuntimeSpecificDefault(getDefaultKeystorePathValue()))
|
.withRuntimeSpecificDefault(getDefaultKeystorePathValue()))
|
||||||
.to("quarkus.http.ssl.certificate.key-store-file")
|
.to(QUARKUS_HTTPS_KEY_STORE_FILE)
|
||||||
|
.transformer(HttpPropertyMappers::transformPath)
|
||||||
.paramLabel("file")
|
.paramLabel("file")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(HttpOptions.HTTPS_KEY_STORE_PASSWORD)
|
fromOption(HttpOptions.HTTPS_KEY_STORE_PASSWORD)
|
||||||
|
@ -91,12 +98,13 @@ public final class HttpPropertyMappers {
|
||||||
.isMasked(true)
|
.isMasked(true)
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(HttpOptions.HTTPS_KEY_STORE_TYPE)
|
fromOption(HttpOptions.HTTPS_KEY_STORE_TYPE)
|
||||||
.to("quarkus.http.ssl.certificate.key-store-file-type")
|
|
||||||
.mapFrom(SecurityOptions.FIPS_MODE, HttpPropertyMappers::resolveKeyStoreType)
|
.mapFrom(SecurityOptions.FIPS_MODE, HttpPropertyMappers::resolveKeyStoreType)
|
||||||
|
.to(QUARKUS_HTTPS_KEY_STORE_FILE_TYPE)
|
||||||
.paramLabel("type")
|
.paramLabel("type")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(HttpOptions.HTTPS_TRUST_STORE_FILE)
|
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")
|
.paramLabel("file")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(HttpOptions.HTTPS_TRUST_STORE_PASSWORD)
|
fromOption(HttpOptions.HTTPS_TRUST_STORE_PASSWORD)
|
||||||
|
@ -105,8 +113,9 @@ public final class HttpPropertyMappers {
|
||||||
.isMasked(true)
|
.isMasked(true)
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(HttpOptions.HTTPS_TRUST_STORE_TYPE)
|
fromOption(HttpOptions.HTTPS_TRUST_STORE_TYPE)
|
||||||
.to("quarkus.http.ssl.certificate.trust-store-file-type")
|
|
||||||
.mapFrom(SecurityOptions.FIPS_MODE, HttpPropertyMappers::resolveKeyStoreType)
|
.mapFrom(SecurityOptions.FIPS_MODE, HttpPropertyMappers::resolveKeyStoreType)
|
||||||
|
.to(QUARKUS_HTTPS_TRUST_STORE_FILE_TYPE)
|
||||||
|
.transformer(HttpPropertyMappers::resolveKeyStoreType)
|
||||||
.paramLabel("type")
|
.paramLabel("type")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(HttpOptions.HTTP_MAX_QUEUED_REQUESTS)
|
fromOption(HttpOptions.HTTP_MAX_QUEUED_REQUESTS)
|
||||||
|
@ -130,10 +139,12 @@ public final class HttpPropertyMappers {
|
||||||
|
|
||||||
public static void validateConfig() {
|
public static void validateConfig() {
|
||||||
boolean enabled = isHttpEnabled(Configuration.getOptionalKcValue(HttpOptions.HTTP_ENABLED.getKey()).orElse(null));
|
boolean enabled = isHttpEnabled(Configuration.getOptionalKcValue(HttpOptions.HTTP_ENABLED.getKey()).orElse(null));
|
||||||
boolean trustStoreFile = Configuration.getOptionalKcValue(HttpOptions.HTTPS_TRUST_STORE_FILE.getKey()).isPresent();
|
Optional<String> certFile = Configuration.getOptionalKcValue(HttpOptions.HTTPS_CERTIFICATE_FILE.getKey());
|
||||||
boolean keyStoreFile = Configuration.getOptionalKcValue(HttpOptions.HTTPS_KEY_STORE_FILE.getKey()).isPresent();
|
Optional<String> 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.trustStoreFile = Configuration.getOptionalKcValue(HttpOptions.HTTPS_TRUST_STORE_FILE.getKey()).map(Paths::get);
|
||||||
|
@ -143,20 +154,7 @@ public final class HttpPropertyMappers {
|
||||||
config.trustStoreCertAlias = Configuration.getOptionalValue("quarkus.http.ssl.certificate.trust-store-cert-alias");
|
config.trustStoreCertAlias = Configuration.getOptionalValue("quarkus.http.ssl.certificate.trust-store-cert-alias");
|
||||||
config.trustStoreFiles = Optional.empty();
|
config.trustStoreFiles = Optional.empty();
|
||||||
|
|
||||||
try {
|
config.keyStoreFile = keystoreFile.map(Paths::get);
|
||||||
TlsUtils.computeTrustOptions(config, config.trustStorePassword);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new PropertyException("Failed to load 'https-trust-store' material.", e);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new PropertyException("Unable to determine 'https-trust-store-type' automatically. " +
|
|
||||||
"Adjust the file extension or specify the property.", 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.keyStorePassword = Configuration.getOptionalKcValue(HttpOptions.HTTPS_KEY_STORE_PASSWORD.getKey());
|
||||||
config.keyStoreFileType = Configuration.getOptionalKcValue(HttpOptions.HTTPS_KEY_STORE_TYPE.getKey());
|
config.keyStoreFileType = Configuration.getOptionalKcValue(HttpOptions.HTTPS_KEY_STORE_TYPE.getKey());
|
||||||
config.keyStoreProvider = Configuration.getOptionalValue("quarkus.http.ssl.certificate.key-store-provider");
|
config.keyStoreProvider = Configuration.getOptionalValue("quarkus.http.ssl.certificate.key-store-provider");
|
||||||
|
@ -164,34 +162,37 @@ public final class HttpPropertyMappers {
|
||||||
config.keyStoreAliasPassword = Configuration.getOptionalValue("quarkus.http.ssl.certificate.key-store-alias-password");
|
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.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.keyStoreKeyAlias = Configuration.getOptionalValue("quarkus.http.ssl.certificate.key-store-key-alias");
|
||||||
config.keyFiles = Optional.empty();
|
|
||||||
config.files = Optional.empty();
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
TlsUtils.computeKeyStoreOptions(config, config.keyStorePassword, config.keyStoreAliasPassword);
|
TlsUtils.computeKeyStoreOptions(config, config.keyStorePassword, config.keyStoreAliasPassword);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new PropertyException("Failed to load 'https-key-store' material.", e);
|
throw new PropertyException("Failed to load 'https-key-' material: " + e.getClass().getSimpleName() + " " + e.getMessage(), e);
|
||||||
} catch (IllegalArgumentException 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. " +
|
throw new PropertyException("Unable to determine 'https-key-store-type' automatically. " +
|
||||||
"Adjust the file extension or specify the property.", e);
|
"Adjust the file extension or specify the property.", e);
|
||||||
}
|
}
|
||||||
}
|
throw new PropertyException(e.getMessage(), e);
|
||||||
|
|
||||||
if (!enabled) {
|
|
||||||
Optional<String> 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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BiFunction<String, ConfigSourceInterceptorContext, String> validatePath(String key) {
|
private static String transformPath(String value, ConfigSourceInterceptorContext context) {
|
||||||
return (value, context) -> Environment.isWindows() && value != null && value.equals(context.proceed(key).getValue()) ? value.replace("\\", "/") : value;
|
return value == null ? value : ClassPathUtils.toResourceName(Path.of(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getHttpEnabledTransformer(String value, ConfigSourceInterceptorContext context) {
|
private static String getHttpEnabledTransformer(String value, ConfigSourceInterceptorContext context) {
|
||||||
|
|
|
@ -288,6 +288,8 @@ public class PropertyMapper<T> {
|
||||||
* NOTE: This transformer will not apply to the mapFrom value. When using
|
* NOTE: This transformer will not apply to the mapFrom value. When using
|
||||||
* {@link #mapFrom} you generally need a transformer specifically for the parent
|
* {@link #mapFrom} you generally need a transformer specifically for the parent
|
||||||
* value, see {@link #mapFrom(Option, BiFunction)}
|
* value, see {@link #mapFrom(Option, BiFunction)}
|
||||||
|
* <p>
|
||||||
|
* The value passed into the transformer may be null if the property has no value set, and no default
|
||||||
*/
|
*/
|
||||||
public Builder<T> transformer(BiFunction<String, ConfigSourceInterceptorContext, String> mapper) {
|
public Builder<T> transformer(BiFunction<String, ConfigSourceInterceptorContext, String> mapper) {
|
||||||
this.mapper = mapper;
|
this.mapper = mapper;
|
||||||
|
|
|
@ -207,6 +207,21 @@ public class PicocliTest extends AbstractConfigurationTest {
|
||||||
+ "\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"));
|
+ "\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
|
@Test
|
||||||
public void failSingleParamWithSpace() {
|
public void failSingleParamWithSpace() {
|
||||||
NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--db postgres");
|
NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--db postgres");
|
||||||
|
|
|
@ -23,6 +23,8 @@ import static org.junit.Assert.assertTrue;
|
||||||
import static org.keycloak.quarkus.runtime.Environment.isWindows;
|
import static org.keycloak.quarkus.runtime.Environment.isWindows;
|
||||||
import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.CLI_ARGS;
|
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.nio.file.Path;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -492,6 +494,17 @@ public class ConfigurationTest extends AbstractConfigurationTest {
|
||||||
assertEquals("2h", createConfig().getConfigValue("quarkus.http.ssl.certificate.reload-period").getValue());
|
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
|
@Test
|
||||||
public void testCacheMaxCount() {
|
public void testCacheMaxCount() {
|
||||||
int maxCount = 500;
|
int maxCount = 500;
|
||||||
|
|
|
@ -173,13 +173,13 @@ public class QuarkusPropertiesDistTest {
|
||||||
@Test
|
@Test
|
||||||
@BeforeStartDistribution(ForceRebuild.class)
|
@BeforeStartDistribution(ForceRebuild.class)
|
||||||
@DisabledOnOs(value = { OS.WINDOWS }, disabledReason = "Windows uses a different path separator.")
|
@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-file=/tmp/kc/bin/../conf/server.crt.pem",
|
||||||
"--https-certificate-key-file=/tmp/kc/bin/../conf/server.key.pem" })
|
"--https-certificate-key-file=/tmp/kc/bin/../conf/server.key.pem" })
|
||||||
@Order(13)
|
@Order(13)
|
||||||
void testHttpCertsPathTransformer(LaunchResult result) {
|
void testHttpCertsPathTransformer(LaunchResult result) {
|
||||||
CLIResult cliResult = (CLIResult) 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
|
@Test
|
||||||
|
@ -191,7 +191,7 @@ public class QuarkusPropertiesDistTest {
|
||||||
@Order(14)
|
@Order(14)
|
||||||
void testHttpCertsPathTransformerOnWindows(LaunchResult result) {
|
void testHttpCertsPathTransformerOnWindows(LaunchResult result) {
|
||||||
CLIResult cliResult = (CLIResult) 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<KeycloakDistribution> {
|
public static class AddConsoleHandlerFromQuarkusProps implements Consumer<KeycloakDistribution> {
|
||||||
|
|
|
@ -150,8 +150,8 @@ Truststore:
|
||||||
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
||||||
DEFAULT. Default: DEFAULT.
|
DEFAULT. Default: DEFAULT.
|
||||||
--truststore-paths <truststore-paths>
|
--truststore-paths <truststore-paths>
|
||||||
List of pkcs12 (p12 or pfx file extensions), PEM files, or directories
|
List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or
|
||||||
containing those files that will be used as a system truststore.
|
directories containing those files that will be used as a system truststore.
|
||||||
|
|
||||||
Export:
|
Export:
|
||||||
|
|
||||||
|
|
|
@ -248,8 +248,8 @@ Truststore:
|
||||||
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
||||||
DEFAULT. Default: DEFAULT.
|
DEFAULT. Default: DEFAULT.
|
||||||
--truststore-paths <truststore-paths>
|
--truststore-paths <truststore-paths>
|
||||||
List of pkcs12 (p12 or pfx file extensions), PEM files, or directories
|
List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or
|
||||||
containing those files that will be used as a system truststore.
|
directories containing those files that will be used as a system truststore.
|
||||||
|
|
||||||
Export:
|
Export:
|
||||||
|
|
||||||
|
|
|
@ -150,8 +150,8 @@ Truststore:
|
||||||
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
||||||
DEFAULT. Default: DEFAULT.
|
DEFAULT. Default: DEFAULT.
|
||||||
--truststore-paths <truststore-paths>
|
--truststore-paths <truststore-paths>
|
||||||
List of pkcs12 (p12 or pfx file extensions), PEM files, or directories
|
List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or
|
||||||
containing those files that will be used as a system truststore.
|
directories containing those files that will be used as a system truststore.
|
||||||
|
|
||||||
Import:
|
Import:
|
||||||
|
|
||||||
|
|
|
@ -248,8 +248,8 @@ Truststore:
|
||||||
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
||||||
DEFAULT. Default: DEFAULT.
|
DEFAULT. Default: DEFAULT.
|
||||||
--truststore-paths <truststore-paths>
|
--truststore-paths <truststore-paths>
|
||||||
List of pkcs12 (p12 or pfx file extensions), PEM files, or directories
|
List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or
|
||||||
containing those files that will be used as a system truststore.
|
directories containing those files that will be used as a system truststore.
|
||||||
|
|
||||||
Import:
|
Import:
|
||||||
|
|
||||||
|
|
|
@ -321,8 +321,8 @@ Truststore:
|
||||||
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
||||||
DEFAULT. Default: DEFAULT.
|
DEFAULT. Default: DEFAULT.
|
||||||
--truststore-paths <truststore-paths>
|
--truststore-paths <truststore-paths>
|
||||||
List of pkcs12 (p12 or pfx file extensions), PEM files, or directories
|
List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or
|
||||||
containing those files that will be used as a system truststore.
|
directories containing those files that will be used as a system truststore.
|
||||||
|
|
||||||
Security:
|
Security:
|
||||||
|
|
||||||
|
|
|
@ -454,8 +454,8 @@ Truststore:
|
||||||
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
||||||
DEFAULT. Default: DEFAULT.
|
DEFAULT. Default: DEFAULT.
|
||||||
--truststore-paths <truststore-paths>
|
--truststore-paths <truststore-paths>
|
||||||
List of pkcs12 (p12 or pfx file extensions), PEM files, or directories
|
List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or
|
||||||
containing those files that will be used as a system truststore.
|
directories containing those files that will be used as a system truststore.
|
||||||
|
|
||||||
Security:
|
Security:
|
||||||
|
|
||||||
|
|
|
@ -322,8 +322,8 @@ Truststore:
|
||||||
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
||||||
DEFAULT. Default: DEFAULT.
|
DEFAULT. Default: DEFAULT.
|
||||||
--truststore-paths <truststore-paths>
|
--truststore-paths <truststore-paths>
|
||||||
List of pkcs12 (p12 or pfx file extensions), PEM files, or directories
|
List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or
|
||||||
containing those files that will be used as a system truststore.
|
directories containing those files that will be used as a system truststore.
|
||||||
|
|
||||||
Security:
|
Security:
|
||||||
|
|
||||||
|
|
|
@ -455,8 +455,8 @@ Truststore:
|
||||||
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
||||||
DEFAULT. Default: DEFAULT.
|
DEFAULT. Default: DEFAULT.
|
||||||
--truststore-paths <truststore-paths>
|
--truststore-paths <truststore-paths>
|
||||||
List of pkcs12 (p12 or pfx file extensions), PEM files, or directories
|
List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or
|
||||||
containing those files that will be used as a system truststore.
|
directories containing those files that will be used as a system truststore.
|
||||||
|
|
||||||
Security:
|
Security:
|
||||||
|
|
||||||
|
|
|
@ -273,8 +273,8 @@ Truststore:
|
||||||
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
||||||
DEFAULT. Default: DEFAULT.
|
DEFAULT. Default: DEFAULT.
|
||||||
--truststore-paths <truststore-paths>
|
--truststore-paths <truststore-paths>
|
||||||
List of pkcs12 (p12 or pfx file extensions), PEM files, or directories
|
List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or
|
||||||
containing those files that will be used as a system truststore.
|
directories containing those files that will be used as a system truststore.
|
||||||
|
|
||||||
Bootstrap Admin:
|
Bootstrap Admin:
|
||||||
|
|
||||||
|
|
|
@ -395,8 +395,8 @@ Truststore:
|
||||||
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
Possible values are: ANY, WILDCARD (deprecated), STRICT (deprecated),
|
||||||
DEFAULT. Default: DEFAULT.
|
DEFAULT. Default: DEFAULT.
|
||||||
--truststore-paths <truststore-paths>
|
--truststore-paths <truststore-paths>
|
||||||
List of pkcs12 (p12 or pfx file extensions), PEM files, or directories
|
List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or
|
||||||
containing those files that will be used as a system truststore.
|
directories containing those files that will be used as a system truststore.
|
||||||
|
|
||||||
Bootstrap Admin:
|
Bootstrap Admin:
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.truststore;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.util.KeystoreUtil;
|
import org.keycloak.common.util.KeystoreUtil;
|
||||||
|
import org.keycloak.common.util.KeystoreUtil.KeystoreFormat;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
@ -101,19 +102,18 @@ public class TruststoreBuilder {
|
||||||
if (f.isDirectory()) {
|
if (f.isDirectory()) {
|
||||||
mergeFiles(Stream.of(f.listFiles()).map(File::getAbsolutePath).toArray(String[]::new), truststore, false, discoveredFiles);
|
mergeFiles(Stream.of(f.listFiles()).map(File::getAbsolutePath).toArray(String[]::new), truststore, false, discoveredFiles);
|
||||||
} else {
|
} 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));
|
mergeTrustStore(truststore, file, loadStore(file, PKCS12, null));
|
||||||
if (!topLevel) {
|
if (!topLevel) {
|
||||||
discoveredFiles.add(f.getAbsolutePath());
|
discoveredFiles.add(f.getAbsolutePath());
|
||||||
}
|
}
|
||||||
} else {
|
} else if (mergePemFile(truststore, file, topLevel) && !topLevel) {
|
||||||
if (mergePemFile(truststore, file, topLevel) && !topLevel) {
|
|
||||||
discoveredFiles.add(f.getAbsolutePath());
|
discoveredFiles.add(f.getAbsolutePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static KeyStore createPkcs12KeyStore() {
|
static KeyStore createPkcs12KeyStore() {
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in a new issue