Merge pull request #103 from rhauch/dbz-122

DBZ-122 Prevent logging of password configuration property values
This commit is contained in:
Randall Hauch 2016-09-21 15:15:02 -05:00 committed by GitHub
commit 3e2d953b1a
3 changed files with 112 additions and 7 deletions

View File

@ -58,6 +58,8 @@
@Immutable
public interface Configuration {
public static final Pattern PASSWORD_PATTERN = Pattern.compile(".*password$", Pattern.CASE_INSENSITIVE);
/**
* The basic interface for configuration builders.
*
@ -734,7 +736,7 @@ public Set<String> keys() {
@Override
public String toString() {
return props.toString();
return withMaskedPasswords().asProperties().toString();
}
};
}
@ -781,7 +783,7 @@ public Set<String> keys() {
@Override
public String toString() {
return props.toString();
return withMaskedPasswords().asProperties().toString();
}
};
}
@ -1442,7 +1444,7 @@ public String getString(String key) {
@Override
public String toString() {
return asProperties().toString();
return withMaskedPasswords().asProperties().toString();
}
};
}
@ -1471,7 +1473,83 @@ public String getString(String key) {
@Override
public String toString() {
return asProperties().toString();
return withMaskedPasswords().asProperties().toString();
}
};
}
/**
* Return a new {@link Configuration} that contains the mapped values.
*
* @param mapper the function that takes a key and value and returns the new mapped value
* @return the Configuration with mapped values; never null
*/
default Configuration mapped(BiFunction<? super String, ? super String, String> mapper) {
if (mapper == null) return this;
return new Configuration() {
@Override
public Set<String> keys() {
return Configuration.this.keys();
}
@Override
public String getString(String key) {
return mapper.apply(key, Configuration.this.getString(key));
}
@Override
public String toString() {
return withMaskedPasswords().asProperties().toString();
}
};
}
/**
* Return a new {@link Configuration} that contains all of the same fields as this configuration, except with masked values
* for all keys that end in "password".
*
* @return the Configuration with masked values for matching keys; never null
*/
default Configuration withMaskedPasswords() {
return withMasked(PASSWORD_PATTERN);
}
/**
* Return a new {@link Configuration} that contains all of the same fields as this configuration, except with masked values
* for all keys that match the specified pattern.
*
* @param keyRegex the regular expression to match against the keys
* @return the Configuration with masked values for matching keys; never null
*/
default Configuration withMasked(String keyRegex) {
if (keyRegex == null) return this;
return withMasked(Pattern.compile(keyRegex));
}
/**
* Return a new {@link Configuration} that contains all of the same fields as this configuration, except with masked values
* for all keys that match the specified pattern.
*
* @param keyRegex the regular expression to match against the keys
* @return the Configuration with masked values for matching keys; never null
*/
default Configuration withMasked(Pattern keyRegex) {
if (keyRegex == null) return this;
return new Configuration() {
@Override
public Set<String> keys() {
return Configuration.this.keys();
}
@Override
public String getString(String key) {
boolean matches = keyRegex.matcher(key).matches();
return matches ? "********" : Configuration.this.getString(key);
}
@Override
public String toString() {
return withMaskedPasswords().asProperties().toString();
}
};
}
@ -1623,7 +1701,13 @@ default boolean validateAndRecord(Iterable<Field> fields, Consumer<String> probl
problems.accept("The '" + f.name() + "' value is invalid: " + problem);
} else {
String valueStr = v.toString();
if (v instanceof CharSequence) valueStr = "'" + valueStr + "'";
if (v instanceof CharSequence) {
if (PASSWORD_PATTERN.matcher((CharSequence) v).matches()) {
valueStr = "********"; // mask any fields that we know are passwords
} else {
valueStr = "'" + valueStr + "'";
}
}
problems.accept("The '" + f.name() + "' value " + valueStr + " is invalid: " + problem);
}
});

View File

@ -137,8 +137,8 @@ public void configure(Configuration config, HistoryRecordComparator comparator)
.withDefault(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class)
.withDefault(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class)
.build();
logger.info("KafkaDatabaseHistory Consumer config: " + consumerConfig);
logger.info("KafkaDatabaseHistory Producer config: " + producerConfig);
logger.info("KafkaDatabaseHistory Consumer config: " + consumerConfig.withMaskedPasswords());
logger.info("KafkaDatabaseHistory Producer config: " + producerConfig.withMaskedPasswords());
}
@Override

View File

@ -7,6 +7,7 @@
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import org.junit.Before;
import org.junit.Test;
@ -90,5 +91,25 @@ public void shouldCallFunctionOnEachMatchingFieldUsingRegex() {
});
assertThat(counter.get()).isEqualTo(6);
}
@Test
public void shouldMaskPasswords() {
Pattern p = Pattern.compile(".*password$",Pattern.CASE_INSENSITIVE);
assertThat(p.matcher("password").matches()).isTrue();
assertThat(p.matcher("otherpassword").matches()).isTrue();
config = Configuration.create()
.with("column.password", "warning")
.with("column.Password.this.is.not","value")
.with("column.truncate.to.20.chars", "20-chars")
.with("column.mask.with.20.chars", "20-mask")
.with("column.mask.with.0.chars", "0-mask")
.with("column.mask.with.chars", "should-not-be-matched")
.build();
assertThat(config.withMaskedPasswords().toString().contains("warning")).isFalse();
assertThat(config.toString().contains("warning")).isFalse();
assertThat(config.withMaskedPasswords().getString("column.password")).isEqualTo("********");
assertThat(config.getString("column.password")).isEqualTo("warning");
}
}