KEYCLOAK-19858: Sanitize CLI Log output and show-config output

This commit is contained in:
Dominik Guhr 2021-11-29 10:54:52 +01:00 committed by Pedro Igor
parent a5c3b83443
commit 7f734c9e68
3 changed files with 77 additions and 23 deletions

View file

@ -25,6 +25,7 @@ import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuiltT
import static org.keycloak.quarkus.runtime.configuration.Configuration.getConfig;
import static org.keycloak.quarkus.runtime.Environment.isDevMode;
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.isBuildTimeProperty;
import static org.keycloak.utils.StringUtil.isNotBlank;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST;
@ -34,6 +35,7 @@ import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -49,13 +51,13 @@ import org.keycloak.quarkus.runtime.cli.command.Main;
import org.keycloak.quarkus.runtime.cli.command.Start;
import org.keycloak.quarkus.runtime.cli.command.StartDev;
import org.keycloak.common.Profile;
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
import org.keycloak.quarkus.runtime.configuration.mappers.ConfigCategory;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
import org.keycloak.quarkus.runtime.Environment;
import io.quarkus.runtime.Quarkus;
import io.smallrye.config.ConfigValue;
import picocli.CommandLine;
import picocli.CommandLine.Model.CommandSpec;
@ -118,11 +120,12 @@ public final class Picocli {
if (hasConfigChanges()) {
cmd.getOut().println("Changes detected in configuration. Updating the server image.");
if(!isDevMode()) {
List<String> cliInput = getSanitizedCliInput();
cmd.getOut().printf("For an optional runtime and bypass this step, please run the '%s' command prior to starting the server:%n%n\t%s %s %s%n",
Build.NAME,
Environment.getCommand(),
Build.NAME,
String.join(" ", asList(ARG_SPLIT.split(Environment.getConfigArgs()))) + "\n");
String.join(" ", cliInput) + "\n");
}
return true;
}
@ -130,6 +133,39 @@ public final class Picocli {
return hasProviderChanges();
}
/**
* checks the raw cli input for possible credentials / properties which should be masked,
* and masks them.
* @return a list of potentially masked properties in CLI format, e.g. `--db-password=*******`
* instead of the actual passwords value.
*/
private static List<String> getSanitizedCliInput() {
if(Environment.getConfigArgs().isEmpty()) {
return Collections.emptyList();
}
List<String> rawCliArgs = asList(ARG_SPLIT.split(Environment.getConfigArgs()));
List<String> properties = new ArrayList<>();
if (!rawCliArgs.isEmpty()) {
for(String rawCliArg : rawCliArgs) {
String rawKey = rawCliArg.split("=")[0];
PropertyMapper mapper = PropertyMappers.getMapper(rawKey);
String value = rawCliArg.split("=")[1];
if (mapper != null) {
value = formatValue(
mapper.getFrom(),
value);
}
properties.add(rawKey + "=" + value);
}
}
return properties;
}
private static void runReAugmentation(List<String> cliArgs, CommandLine cmd) {
List<String> configArgsList = new ArrayList<>(cliArgs);

View file

@ -22,6 +22,7 @@ import java.util.function.BiFunction;
import io.smallrye.config.ConfigSourceInterceptorContext;
import io.smallrye.config.ConfigValue;
import org.keycloak.quarkus.runtime.cli.Picocli;
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
public class PropertyMapper {
@ -46,6 +47,8 @@ public class PropertyMapper {
private final ConfigCategory category;
private final String paramLabel;
private final boolean hidden;
private String cliFormat;
PropertyMapper(String from, String to, String defaultValue, BiFunction<String, ConfigSourceInterceptorContext, String> mapper,
String mapFrom, boolean buildTime, String description, String paramLabel, boolean mask, Iterable<String> expectedValues,
@ -62,6 +65,7 @@ public class PropertyMapper {
this.expectedValues = expectedValues == null ? Collections.emptyList() : expectedValues;
this.category = category != null ? category : ConfigCategory.GENERAL;
this.hidden = hidden;
setCliFormat(this.from);
}
public static PropertyMapper.Builder builder(String fromProp, String toProp) {
@ -163,6 +167,26 @@ public class PropertyMapper {
return hidden;
}
public boolean isBuildTime() {
return buildTime;
}
public String getTo() {
return to;
}
public String getParamLabel() {
return paramLabel;
}
public String getCliFormat() {
return cliFormat;
}
boolean isMask() {
return mask;
}
private ConfigValue transformValue(String value, ConfigSourceInterceptorContext context) {
if (value == null) {
return null;
@ -181,20 +205,8 @@ public class PropertyMapper {
return null;
}
public boolean isBuildTime() {
return buildTime;
}
boolean isMask() {
return mask;
}
public String getTo() {
return to;
}
public String getParamLabel() {
return paramLabel;
private void setCliFormat(String from) {
cliFormat = Picocli.ARG_PREFIX + PropertyMappers.toCLIFormat(from).substring(3);
}
public static class Builder {

View file

@ -15,6 +15,7 @@ import java.util.stream.Collectors;
public final class PropertyMappers {
public static String VALUE_MASK = "*******";
static final MappersConfig MAPPERS = new MappersConfig();
private PropertyMappers(){}
@ -96,6 +97,7 @@ public final class PropertyMappers {
if (name.indexOf('.') == -1) {
return name;
}
return MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX
.concat(name.substring(3, name.lastIndexOf('.') + 1)
.replaceAll("\\.", "-") + name.substring(name.lastIndexOf('.') + 1));
@ -116,23 +118,26 @@ public final class PropertyMappers {
}
public static String formatValue(String property, String value) {
property = removeProfilePrefixIfNeeded(property);
PropertyMapper mapper = getMapper(property);
if (mapper != null && mapper.isMask()) {
return "*******";
return VALUE_MASK;
}
return value;
}
public static PropertyMapper getMapper(String property) {
PropertyMapper mapper = MAPPERS.get(property);
if (mapper != null) {
return mapper;
private static String removeProfilePrefixIfNeeded(String property) {
if(property.startsWith("%")) {
String profilePrefix = property.substring(0, property.indexOf(".") +1);
property = property.split(profilePrefix)[1];
}
return property;
}
return null;
public static PropertyMapper getMapper(String property) {
return MAPPERS.get(property);
}
public static Collection<PropertyMapper> getMappers() {
@ -164,6 +169,7 @@ public final class PropertyMappers {
for (PropertyMapper mapper : mappers) {
super.put(mapper.getTo(), mapper);
super.put(mapper.getFrom(), mapper);
super.put(mapper.getCliFormat(), mapper);
}
}