Skip to content

Commit

Permalink
Obsfucate secret configs in more places
Browse files Browse the repository at this point in the history
* the logger passed to Configs, and called on updates
* the results from dbms.listConfigs

Even though these are admin actions, so is reading the debug.log, so
we obsfucate these cases for the same reason.
  • Loading branch information
craigtaverner committed Apr 4, 2018
1 parent 382113f commit 047f7fb
Show file tree
Hide file tree
Showing 12 changed files with 85 additions and 14 deletions.
Expand Up @@ -61,7 +61,7 @@ public List<ConfigValue> asConfigValues( @Nonnull Map<String,String> validConfig
setting.documentedDefaultValue(), setting.documentedDefaultValue(),
Optional.ofNullable( val.getValue() ), Optional.ofNullable( val.getValue() ),
setting.valueDescription(), setting.internal(), setting.dynamic(), setting.valueDescription(), setting.internal(), setting.dynamic(),
setting.deprecated(), setting.replacement(), setting.isSecret() ); setting.deprecated(), setting.replacement(), setting.secret() );
} ) } )
.collect( Collectors.toList() ); .collect( Collectors.toList() );
} }
Expand Down
Expand Up @@ -77,7 +77,7 @@ public Optional<?> value()
@Nonnull @Nonnull
public Optional<String> valueAsString() public Optional<String> valueAsString()
{ {
return value.map( ConfigValue::valueToString ); return this.isSecret() ? Optional.of( Secret.OBSFUCATED ) : value.map( ConfigValue::valueToString );
} }


@Override @Override
Expand Down
Expand Up @@ -77,7 +77,7 @@ default List<ConfigOptions> getConfigOptions()
setting.setInternal( internalAnnotation != null ); setting.setInternal( internalAnnotation != null );


final Secret secretAnnotation = f.getAnnotation( Secret.class ); final Secret secretAnnotation = f.getAnnotation( Secret.class );
setting.makeSecret( secretAnnotation != null ); setting.setSecret( secretAnnotation != null );


final Dynamic dynamicAnnotation = f.getAnnotation( Dynamic.class ); final Dynamic dynamicAnnotation = f.getAnnotation( Dynamic.class );
setting.setDynamic( dynamicAnnotation != null ); setting.setDynamic( dynamicAnnotation != null );
Expand Down
Expand Up @@ -26,6 +26,7 @@


/** /**
* Used to define a configuration setting as secret, which means we should not show the value in logs. * Used to define a configuration setting as secret, which means we should not show the value in logs.
* It is supported in configs set using LoadableConfig, but not for group settings, like connectors and SSL policies.
*/ */
@Retention( RetentionPolicy.RUNTIME ) @Retention( RetentionPolicy.RUNTIME )
@Target( {ElementType.TYPE, ElementType.FIELD} ) @Target( {ElementType.TYPE, ElementType.FIELD} )
Expand Down
Expand Up @@ -42,6 +42,7 @@ public void handlesEmptyValue() throws Exception
assertFalse( value.deprecated() ); assertFalse( value.deprecated() );
assertEquals( Optional.empty(), value.replacement() ); assertEquals( Optional.empty(), value.replacement() );
assertFalse( value.internal() ); assertFalse( value.internal() );
assertFalse( value.isSecret() );
} }


@Test @Test
Expand All @@ -51,6 +52,7 @@ public void handlesInternal() throws Exception
"description", true, false, false, Optional.empty(), false ); "description", true, false, false, Optional.empty(), false );


assertTrue( value.internal() ); assertTrue( value.internal() );
assertFalse( value.isSecret() );
} }


@Test @Test
Expand All @@ -64,6 +66,7 @@ public void handlesNonEmptyValue() throws Exception
assertFalse( value.deprecated() ); assertFalse( value.deprecated() );
assertEquals( Optional.empty(), value.replacement() ); assertEquals( Optional.empty(), value.replacement() );
assertFalse( value.internal() ); assertFalse( value.internal() );
assertFalse( value.isSecret() );
} }


@Test @Test
Expand All @@ -77,6 +80,7 @@ public void handlesDeprecationAndReplacement() throws Exception
assertTrue( value.deprecated() ); assertTrue( value.deprecated() );
assertEquals( "new_name", value.replacement().get() ); assertEquals( "new_name", value.replacement().get() );
assertFalse( value.internal() ); assertFalse( value.internal() );
assertFalse( value.isSecret() );
} }


@Test @Test
Expand All @@ -90,9 +94,24 @@ public void handlesValueDescription() throws Exception
assertTrue( value.deprecated() ); assertTrue( value.deprecated() );
assertEquals( "new_name", value.replacement().get() ); assertEquals( "new_name", value.replacement().get() );
assertFalse( value.internal() ); assertFalse( value.internal() );
assertFalse( value.isSecret() );
assertEquals( "a simple integer", value.valueDescription() ); assertEquals( "a simple integer", value.valueDescription() );
} }


@Test
public void handlesSecretValue() throws Exception
{
ConfigValue value = new ConfigValue( "name", Optional.empty(), Optional.empty(), Optional.of( "secret" ),
"description", false, false, false, Optional.empty(), true );

assertEquals( Optional.of( "secret" ), value.value() );
assertEquals( Secret.OBSFUCATED, value.toString() );
assertFalse( value.deprecated() );
assertEquals( Optional.empty(), value.replacement() );
assertFalse( value.internal() );
assertTrue( value.isSecret() );
}

@Test @Test
public void durationValueIsRepresentedWithUnit() throws Exception public void durationValueIsRepresentedWithUnit() throws Exception
{ {
Expand Down
Expand Up @@ -70,12 +70,12 @@ public void setInternal( final boolean val )
} }


@Override @Override
public boolean isSecret() public boolean secret()
{ {
return this.isSecret; return this.isSecret;
} }


public void makeSecret( final boolean val ) public void setSecret( final boolean val )
{ {
this.isSecret = val; this.isSecret = val;
} }
Expand Down
Expand Up @@ -63,7 +63,10 @@ public interface SettingGroup<T> extends SettingValidator
/** /**
* @return {@code true} if secret setting (should be hidden), false otherwise. * @return {@code true} if secret setting (should be hidden), false otherwise.
*/ */
boolean isSecret(); default boolean secret()
{
return false;
}


/** /**
* @return the documented default value if it needs special documentation, empty if default value is good as is. * @return the documented default value if it needs special documentation, empty if default value is good as is.
Expand Down
Expand Up @@ -397,7 +397,7 @@ private Config( File configFile,
// Find secret settings // Find secret settings
configOptions.stream() configOptions.stream()
.map( ConfigOptions::settingGroup ) .map( ConfigOptions::settingGroup )
.filter( SettingGroup::isSecret ) .filter( SettingGroup::secret )
.filter( BaseSetting.class::isInstance ) .filter( BaseSetting.class::isInstance )
.map( BaseSetting.class::cast ) .map( BaseSetting.class::cast )
.forEach( setting -> secrets.add( setting.name() ) ); .forEach( setting -> secrets.add( setting.name() ) );
Expand Down Expand Up @@ -645,9 +645,11 @@ public void updateDynamicSetting( String setting, String update )
} }
newValue = update; newValue = update;
} }
String oldValueForLog = obsfucateIfSecret( setting, oldValue );
String newValueForLog = obsfucateIfSecret( setting, newValue );
log.info( "Setting changed: '%s' changed from '%s' to '%s'", log.info( "Setting changed: '%s' changed from '%s' to '%s'",
setting, oldValueIsDefault ? "default (" + oldValue + ")" : oldValue, setting, oldValueIsDefault ? "default (" + oldValueForLog + ")" : oldValueForLog,
newValueIsDefault ? "default (" + newValue + ")" : newValue ); newValueIsDefault ? "default (" + newValueForLog + ")" : newValueForLog );
updateListeners.getOrDefault( setting, emptyList() ).forEach( l -> l.accept( oldValue, newValue ) ); updateListeners.getOrDefault( setting, emptyList() ).forEach( l -> l.accept( oldValue, newValue ) );
} }
} }
Expand Down Expand Up @@ -760,7 +762,12 @@ public void dump( DiagnosticsPhase phase, Logger logger )


private String obsfucateIfSecret( Map.Entry<String,String> param ) private String obsfucateIfSecret( Map.Entry<String,String> param )
{ {
return secrets.contains( param.getKey() ) ? Secret.OBSFUCATED : param.getValue(); return obsfucateIfSecret( param.getKey(), param.getValue() );
}

private String obsfucateIfSecret( String key, String value )
{
return secrets.contains( key ) ? Secret.OBSFUCATED : value;
} }


/** /**
Expand Down
Expand Up @@ -229,7 +229,7 @@ public boolean internal()
} }


@Override @Override
public boolean isSecret() public boolean secret()
{ {
return false; return false;
} }
Expand Down
Expand Up @@ -1228,9 +1228,9 @@ public boolean internal()
} }


@Override @Override
public boolean isSecret() public boolean secret()
{ {
return newSetting.isSecret(); return newSetting.secret();
} }


@Override @Override
Expand Down
Expand Up @@ -158,7 +158,7 @@ public boolean internal()
} }


@Override @Override
public boolean isSecret() public boolean secret()
{ {
return false; return false;
} }
Expand Down
Expand Up @@ -19,6 +19,7 @@
*/ */
package org.neo4j.kernel.configuration; package org.neo4j.kernel.configuration;


import org.hamcrest.CoreMatchers;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
Expand Down Expand Up @@ -49,6 +50,7 @@
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
Expand Down Expand Up @@ -390,6 +392,10 @@ public static class MyDynamicSettings implements LoadableConfig
{ {
@Dynamic @Dynamic
public static final Setting<Boolean> boolSetting = setting( "bool_setting", BOOLEAN, Settings.TRUE ); public static final Setting<Boolean> boolSetting = setting( "bool_setting", BOOLEAN, Settings.TRUE );

@Dynamic
@Secret
public static final Setting<String> secretSetting = setting( "password", STRING, "secret" );
} }


@Test @Test
Expand Down Expand Up @@ -470,4 +476,39 @@ public void updateDynamicShouldLogExceptionsFromUpdateListeners() throws Excepti
verify( log ).error( "Failure when notifying listeners after dynamic setting change; " + verify( log ).error( "Failure when notifying listeners after dynamic setting change; " +
"new setting might not have taken effect: Boo", exception ); "new setting might not have taken effect: Boo", exception );
} }

@Test
public void updateDynamicShouldWorkWithSecret() throws Exception
{
// Given a secret dynamic setting with a registered update listener
String settingName = MyDynamicSettings.secretSetting.name();
String changedMessage = "Setting changed: '%s' changed from '%s' to '%s'";
Config config = Config.builder().withConfigClasses( singletonList( new MyDynamicSettings() ) ).build();

Log log = mock( Log.class );
config.setLogger( log );

AtomicInteger counter = new AtomicInteger( 0 );
config.registerDynamicUpdateListener( MyDynamicSettings.secretSetting, ( previous, update ) ->
{
counter.getAndIncrement();
assertThat( "Update listener should not see obsfucated secret", previous, not( CoreMatchers.equalTo( Secret.OBSFUCATED ) ) );
assertThat( "Update listener should not see obsfucated secret", update, not( CoreMatchers.equalTo( Secret.OBSFUCATED ) ) );
} );

// When changing secret settings three times
config.updateDynamicSetting( settingName, "another" );
config.updateDynamicSetting( settingName, "secret2" );
config.updateDynamicSetting( settingName, "" );

// Then we should see obsfucated log messages
InOrder order = inOrder( log );
order.verify( log ).info( changedMessage, settingName, "default (" + Secret.OBSFUCATED + ")", Secret.OBSFUCATED );
order.verify( log ).info( changedMessage, settingName, Secret.OBSFUCATED, Secret.OBSFUCATED );
order.verify( log ).info( changedMessage, settingName, Secret.OBSFUCATED, "default (" + Secret.OBSFUCATED + ")" );
verifyNoMoreInteractions( log );

// And see 3 calls to the update listener
assertThat( counter.get(), is( 3 ) );
}
} }

0 comments on commit 047f7fb

Please sign in to comment.