Enhance masking around config-keystore (#30348)

Closes #30346

Signed-off-by: Václav Muzikář <vmuzikar@redhat.com>
This commit is contained in:
Václav Muzikář 2024-06-12 08:54:45 +02:00 committed by GitHub
parent e6df8a2866
commit 375ea9da03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 40 additions and 12 deletions

View file

@ -124,7 +124,7 @@ After executing the command, you will be prompted to *Enter the password to be s
When the KeyStore is created, you can start the server using the following parameters: When the KeyStore is created, you can start the server using the following parameters:
<@kc.start parameters="--config-keystore=/path/to/keystore.p12 --config-keystore-password=storepass --config-keystore-type=PKCS12"/> <@kc.start parameters="--config-keystore=/path/to/keystore.p12 --config-keystore-password=keystorepass --config-keystore-type=PKCS12"/>
=== Format for raw Quarkus properties === Format for raw Quarkus properties
In most cases, the available configuration options should suffice to configure the server. In most cases, the available configuration options should suffice to configure the server.

View file

@ -34,7 +34,7 @@ import static org.keycloak.quarkus.runtime.configuration.Configuration.getCurren
import static org.keycloak.quarkus.runtime.configuration.Configuration.getRawPersistedProperty; import static org.keycloak.quarkus.runtime.configuration.Configuration.getRawPersistedProperty;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getRuntimeProperty; import static org.keycloak.quarkus.runtime.configuration.Configuration.getRuntimeProperty;
import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX; import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers.formatValue; import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers.maskValue;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers.isBuildTimeProperty; import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers.isBuildTimeProperty;
import static org.keycloak.utils.StringUtil.isNotBlank; import static org.keycloak.utils.StringUtil.isNotBlank;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST; import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST;
@ -222,7 +222,7 @@ public final class Picocli {
return; return;
} }
properties.add(key + "=" + formatValue(key, value)); properties.add(key + "=" + maskValue(key, value));
} }
}, arg -> { }, arg -> {
properties.add(arg); properties.add(arg);

View file

@ -22,7 +22,7 @@ import static org.keycloak.quarkus.runtime.Environment.setProfile;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getConfigValue; import static org.keycloak.quarkus.runtime.configuration.Configuration.getConfigValue;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getPropertyNames; import static org.keycloak.quarkus.runtime.configuration.Configuration.getPropertyNames;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getRuntimeProperty; import static org.keycloak.quarkus.runtime.configuration.Configuration.getRuntimeProperty;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers.formatValue; import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers.maskValue;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -148,7 +148,9 @@ public final class ShowConfig extends AbstractCommand implements Runnable {
value = getRuntimeProperty(property).orElse(value); value = getRuntimeProperty(property).orElse(value);
} }
spec.commandLine().getOut().printf("\t%s = %s (%s)%n", configValue.getName(), formatValue(configValue.getName(), value), KeycloakConfigSourceProvider.getConfigSourceDisplayName(configValue.getConfigSourceName())); value = maskValue(configValue.getName(), value, configValue.getConfigSourceName());
spec.commandLine().getOut().printf("\t%s = %s (%s)%n", configValue.getName(), value, KeycloakConfigSourceProvider.getConfigSourceDisplayName(configValue.getConfigSourceName()));
} }
private static String groupProperties(String property) { private static String groupProperties(String property) {

View file

@ -100,9 +100,13 @@ public class KeycloakConfigSourceProvider implements ConfigSourceProvider, Confi
if (configSource == null) { if (configSource == null) {
return "Derived"; return "Derived";
} }
if (configSource.startsWith("KeyStoreConfigSource")) { if (isKeyStoreConfigSource(configSource)) {
return "config-keystore"; return "config-keystore";
} }
return CONFIG_SOURCE_DISPLAY_NAMES.getOrDefault(configSource, configSource); return CONFIG_SOURCE_DISPLAY_NAMES.getOrDefault(configSource, configSource);
} }
public static boolean isKeyStoreConfigSource(String configSourceName) {
return configSourceName.contains("KeyStoreConfigSource");
}
} }

View file

@ -29,6 +29,7 @@ final class ConfigKeystorePropertyMappers {
.to(SMALLRYE_KEYSTORE_PASSWORD) .to(SMALLRYE_KEYSTORE_PASSWORD)
.transformer(ConfigKeystorePropertyMappers::validatePassword) .transformer(ConfigKeystorePropertyMappers::validatePassword)
.paramLabel("config-keystore-password") .paramLabel("config-keystore-password")
.isMasked(true)
.build(), .build(),
fromOption(ConfigKeystoreOptions.CONFIG_KEYSTORE_TYPE) fromOption(ConfigKeystoreOptions.CONFIG_KEYSTORE_TYPE)
.to("smallrye.config.source.keystore.kc-default.type") .to("smallrye.config.source.keystore.kc-default.type")

View file

@ -34,6 +34,7 @@ import java.util.stream.Collectors;
import static org.keycloak.quarkus.runtime.Environment.isParsedCommand; import static org.keycloak.quarkus.runtime.Environment.isParsedCommand;
import static org.keycloak.quarkus.runtime.Environment.isRebuild; import static org.keycloak.quarkus.runtime.Environment.isRebuild;
import static org.keycloak.quarkus.runtime.Environment.isRebuildCheck; import static org.keycloak.quarkus.runtime.Environment.isRebuildCheck;
import static org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider.isKeyStoreConfigSource;
public final class PropertyMappers { public final class PropertyMappers {
@ -125,11 +126,15 @@ public final class PropertyMappers {
MAPPERS.sanitizeDisabledMappers(); MAPPERS.sanitizeDisabledMappers();
} }
public static String formatValue(String property, String value) { public static String maskValue(String property, String value) {
return maskValue(property, value, null);
}
public static String maskValue(String property, String value, String configSourceName) {
property = removeProfilePrefixIfNeeded(property); property = removeProfilePrefixIfNeeded(property);
PropertyMapper<?> mapper = getMapper(property); PropertyMapper<?> mapper = getMapper(property);
if (mapper != null && mapper.isMask()) { if ((configSourceName != null && isKeyStoreConfigSource(configSourceName) || (mapper != null && mapper.isMask()))) {
return VALUE_MASK; return VALUE_MASK;
} }

View file

@ -17,17 +17,19 @@
package org.keycloak.it.cli; package org.keycloak.it.cli;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.keycloak.it.junit5.extension.CLITest; import org.keycloak.it.junit5.extension.CLITest;
import org.keycloak.it.junit5.extension.ConfigurationTestResource; import org.keycloak.it.junit5.extension.ConfigurationTestResource;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
import org.keycloak.quarkus.runtime.cli.command.ShowConfig; import org.keycloak.quarkus.runtime.cli.command.ShowConfig;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.keycloak.quarkus.runtime.cli.command.Main.CONFIG_FILE_LONG_NAME; import static org.keycloak.quarkus.runtime.cli.command.Main.CONFIG_FILE_LONG_NAME;
@QuarkusTestResource(value = ConfigurationTestResource.class, restrictToAnnotatedClass = true) @QuarkusTestResource(value = ConfigurationTestResource.class, restrictToAnnotatedClass = true)
@ -59,4 +61,16 @@ public class ShowConfigCommandTest {
Assertions.assertFalse(output.contains("testpw3")); Assertions.assertFalse(output.contains("testpw3"));
Assertions.assertTrue(output.contains("kc.db-password = " + PropertyMappers.VALUE_MASK)); Assertions.assertTrue(output.contains("kc.db-password = " + PropertyMappers.VALUE_MASK));
} }
@Test
@Launch({ CONFIG_FILE_LONG_NAME+"=src/test/resources/ShowConfigCommandTest/keycloak-keystore.conf", ShowConfig.NAME, "all" })
void testSmallRyeKeyStoreConfigSource(LaunchResult result) {
// keystore is shared with QuarkusPropertiesDistTest#testSmallRyeKeyStoreConfigSource
String output = result.getOutput();
assertThat(output, containsString("kc.config-keystore-password = " + PropertyMappers.VALUE_MASK));
assertThat(output, containsString("kc.log-level = " + PropertyMappers.VALUE_MASK));
assertThat(output, not(containsString("secret")));
assertThat(output, not(containsString("debug")));
}
} }

View file

@ -0,0 +1,2 @@
config-keystore=src/test/resources/keystore
config-keystore-password=secret