Skip to content

Commit

Permalink
Obsfucate the LDAP system password in toString and debug.log
Browse files Browse the repository at this point in the history
  • Loading branch information
craigtaverner committed Mar 27, 2018
1 parent 16214fe commit 382113f
Show file tree
Hide file tree
Showing 14 changed files with 157 additions and 16 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.deprecated(), setting.replacement(), setting.isSecret() );
} ) } )
.collect( Collectors.toList() ); .collect( Collectors.toList() );
} }
Expand Down
Expand Up @@ -34,21 +34,23 @@ public class ConfigValue
private final Optional<?> value; private final Optional<?> value;
private final String valueDescription; private final String valueDescription;
private final boolean internal; private final boolean internal;
private final boolean isSecret;
private final boolean dynamic; private final boolean dynamic;
private final boolean deprecated; private final boolean deprecated;
private final Optional<String> replacement; private final Optional<String> replacement;


public ConfigValue( @Nonnull String name, @Nonnull Optional<String> description, public ConfigValue( @Nonnull String name, @Nonnull Optional<String> description,
@Nonnull Optional<String> documentedDefaultValue, @Nonnull Optional<?> value, @Nonnull Optional<String> documentedDefaultValue, @Nonnull Optional<?> value,
@Nonnull String valueDescription, boolean internal, boolean dynamic, boolean deprecated, @Nonnull String valueDescription, boolean internal, boolean dynamic, boolean deprecated,
@Nonnull Optional<String> replacement ) @Nonnull Optional<String> replacement, boolean isSecret )
{ {
this.name = name; this.name = name;
this.description = description; this.description = description;
this.documentedDefaultValue = documentedDefaultValue; this.documentedDefaultValue = documentedDefaultValue;
this.value = value; this.value = value;
this.valueDescription = valueDescription; this.valueDescription = valueDescription;
this.internal = internal; this.internal = internal;
this.isSecret = isSecret;
this.dynamic = dynamic; this.dynamic = dynamic;
this.deprecated = deprecated; this.deprecated = deprecated;
this.replacement = replacement; this.replacement = replacement;
Expand Down Expand Up @@ -100,6 +102,11 @@ public boolean internal()
return internal; return internal;
} }


public boolean isSecret()
{
return isSecret;
}

public boolean dynamic() public boolean dynamic()
{ {
return dynamic; return dynamic;
Expand Down
Expand Up @@ -76,6 +76,9 @@ default List<ConfigOptions> getConfigOptions()
final Internal internalAnnotation = f.getAnnotation( Internal.class ); final Internal internalAnnotation = f.getAnnotation( Internal.class );
setting.setInternal( internalAnnotation != null ); setting.setInternal( internalAnnotation != null );


final Secret secretAnnotation = f.getAnnotation( Secret.class );
setting.makeSecret( 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
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2002-2018 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.configuration;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Used to define a configuration setting as secret, which means we should not show the value in logs.
*/
@Retention( RetentionPolicy.RUNTIME )
@Target( {ElementType.TYPE, ElementType.FIELD} )
public @interface Secret
{
String OBSFUCATED = "##########";
}
Expand Up @@ -35,7 +35,7 @@ public class ConfigValueTest
public void handlesEmptyValue() throws Exception public void handlesEmptyValue() throws Exception
{ {
ConfigValue value = new ConfigValue( "name", Optional.empty(), Optional.empty(), Optional.empty(), ConfigValue value = new ConfigValue( "name", Optional.empty(), Optional.empty(), Optional.empty(),
"description", false, false, false, Optional.empty() ); "description", false, false, false, Optional.empty(), false );


assertEquals( Optional.empty(), value.value() ); assertEquals( Optional.empty(), value.value() );
assertEquals( "null", value.toString() ); assertEquals( "null", value.toString() );
Expand All @@ -48,8 +48,7 @@ public void handlesEmptyValue() throws Exception
public void handlesInternal() throws Exception public void handlesInternal() throws Exception
{ {
ConfigValue value = new ConfigValue( "name", Optional.empty(), Optional.empty(), Optional.empty(), ConfigValue value = new ConfigValue( "name", Optional.empty(), Optional.empty(), Optional.empty(),
"description", true, false, false, "description", true, false, false, Optional.empty(), false );
Optional.empty() );


assertTrue( value.internal() ); assertTrue( value.internal() );
} }
Expand All @@ -58,7 +57,7 @@ public void handlesInternal() throws Exception
public void handlesNonEmptyValue() throws Exception public void handlesNonEmptyValue() throws Exception
{ {
ConfigValue value = new ConfigValue( "name", Optional.empty(), Optional.empty(), Optional.of( 1 ), ConfigValue value = new ConfigValue( "name", Optional.empty(), Optional.empty(), Optional.of( 1 ),
"description", false, false, false, Optional.empty() ); "description", false, false, false, Optional.empty(), false );


assertEquals( Optional.of( 1 ), value.value() ); assertEquals( Optional.of( 1 ), value.value() );
assertEquals( "1", value.toString() ); assertEquals( "1", value.toString() );
Expand All @@ -71,8 +70,7 @@ public void handlesNonEmptyValue() throws Exception
public void handlesDeprecationAndReplacement() throws Exception public void handlesDeprecationAndReplacement() throws Exception
{ {
ConfigValue value = new ConfigValue( "old_name", Optional.empty(), Optional.empty(), Optional.of( 1 ), ConfigValue value = new ConfigValue( "old_name", Optional.empty(), Optional.empty(), Optional.of( 1 ),
"description", false, false, true, "description", false, false, true, Optional.of( "new_name" ), false );
Optional.of( "new_name" ) );


assertEquals( Optional.of( 1 ), value.value() ); assertEquals( Optional.of( 1 ), value.value() );
assertEquals( "1", value.toString() ); assertEquals( "1", value.toString() );
Expand All @@ -85,8 +83,7 @@ public void handlesDeprecationAndReplacement() throws Exception
public void handlesValueDescription() throws Exception public void handlesValueDescription() throws Exception
{ {
ConfigValue value = new ConfigValue( "old_name", Optional.empty(), Optional.empty(), Optional.of( 1 ), ConfigValue value = new ConfigValue( "old_name", Optional.empty(), Optional.empty(), Optional.of( 1 ),
"a simple integer", false, false, true, "a simple integer", false, false, true, Optional.of( "new_name" ), false );
Optional.of( "new_name" ) );


assertEquals( Optional.of( 1 ), value.value() ); assertEquals( Optional.of( 1 ), value.value() );
assertEquals( "1", value.toString() ); assertEquals( "1", value.toString() );
Expand Down
Expand Up @@ -31,6 +31,7 @@ public abstract class BaseSetting<T> implements Setting<T>
private boolean deprecated; private boolean deprecated;
private String replacement; private String replacement;
private boolean internal; private boolean internal;
private boolean isSecret;
private boolean dynamic; private boolean dynamic;
private String documentedDefaultValue; private String documentedDefaultValue;
private String description; private String description;
Expand Down Expand Up @@ -68,6 +69,17 @@ public void setInternal( final boolean val )
this.internal = val; this.internal = val;
} }


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

public void makeSecret( final boolean val )
{
this.isSecret = val;
}

@Override @Override
public Optional<String> documentedDefaultValue() public Optional<String> documentedDefaultValue()
{ {
Expand Down
Expand Up @@ -60,6 +60,11 @@ public interface SettingGroup<T> extends SettingValidator
*/ */
boolean internal(); boolean internal();


/**
* @return {@code true} if secret setting (should be hidden), false otherwise.
*/
boolean isSecret();

/** /**
* @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 @@ -26,6 +26,7 @@
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
Expand All @@ -44,10 +45,12 @@
import org.neo4j.configuration.ConfigOptions; import org.neo4j.configuration.ConfigOptions;
import org.neo4j.configuration.ConfigValue; import org.neo4j.configuration.ConfigValue;
import org.neo4j.configuration.LoadableConfig; import org.neo4j.configuration.LoadableConfig;
import org.neo4j.configuration.Secret;
import org.neo4j.graphdb.config.BaseSetting; import org.neo4j.graphdb.config.BaseSetting;
import org.neo4j.graphdb.config.Configuration; import org.neo4j.graphdb.config.Configuration;
import org.neo4j.graphdb.config.InvalidSettingException; import org.neo4j.graphdb.config.InvalidSettingException;
import org.neo4j.graphdb.config.Setting; import org.neo4j.graphdb.config.Setting;
import org.neo4j.graphdb.config.SettingGroup;
import org.neo4j.graphdb.config.SettingValidator; import org.neo4j.graphdb.config.SettingValidator;
import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.helpers.collection.MapUtil; import org.neo4j.helpers.collection.MapUtil;
Expand Down Expand Up @@ -82,6 +85,7 @@ public class Config implements DiagnosticsProvider, Configuration
private final List<ConfigOptions> configOptions; private final List<ConfigOptions> configOptions;


private final Map<String,String> params = new CopyOnWriteHashMap<>(); // Read heavy workload private final Map<String,String> params = new CopyOnWriteHashMap<>(); // Read heavy workload
private final Set<String> secrets = new HashSet<>();
private final Map<String, Collection<BiConsumer<String,String>>> updateListeners = new ConcurrentHashMap<>(); private final Map<String, Collection<BiConsumer<String,String>>> updateListeners = new ConcurrentHashMap<>();
private final ConfigurationMigrator migrator; private final ConfigurationMigrator migrator;
private final List<ConfigurationValidator> validators = new ArrayList<>(); private final List<ConfigurationValidator> validators = new ArrayList<>();
Expand Down Expand Up @@ -390,6 +394,14 @@ private Config( File configFile,
.map( BaseSetting.class::cast ) .map( BaseSetting.class::cast )
.forEach( setting -> settingsMap.put( setting.name(), setting ) ); .forEach( setting -> settingsMap.put( setting.name(), setting ) );


// Find secret settings
configOptions.stream()
.map( ConfigOptions::settingGroup )
.filter( SettingGroup::isSecret )
.filter( BaseSetting.class::isInstance )
.map( BaseSetting.class::cast )
.forEach( setting -> secrets.add( setting.name() ) );

validators.addAll( additionalValidators ); validators.addAll( additionalValidators );
migrator = new AnnotationBasedConfigurationMigrator( settingsClasses ); migrator = new AnnotationBasedConfigurationMigrator( settingsClasses );
this.overriddenDefaults.putAll( overriddenDefaults ); this.overriddenDefaults.putAll( overriddenDefaults );
Expand Down Expand Up @@ -741,11 +753,16 @@ public void dump( DiagnosticsPhase phase, Logger logger )
logger.log( "Neo4j Kernel properties:" ); logger.log( "Neo4j Kernel properties:" );
for ( Map.Entry<String,String> param : params.entrySet() ) for ( Map.Entry<String,String> param : params.entrySet() )
{ {
logger.log( "%s=%s", param.getKey(), param.getValue() ); logger.log( "%s=%s", param.getKey(), obsfucateIfSecret( param ) );
} }
} }
} }


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

/** /**
* Migrates and validates all string values in the provided <code>settings</code> map. * Migrates and validates all string values in the provided <code>settings</code> map.
* *
Expand Down Expand Up @@ -942,7 +959,7 @@ public String toString()
{ {
return params.entrySet().stream() return params.entrySet().stream()
.sorted( Comparator.comparing( Map.Entry::getKey ) ) .sorted( Comparator.comparing( Map.Entry::getKey ) )
.map( entry -> entry.getKey() + "=" + entry.getValue() ) .map( entry -> entry.getKey() + "=" + obsfucateIfSecret( entry ) )
.collect( Collectors.joining( ", ") ); .collect( Collectors.joining( ", ") );
} }
} }
Expand Up @@ -228,6 +228,12 @@ public boolean internal()
return false; return false;
} }


@Override
public boolean isSecret()
{
return false;
}

@Override @Override
public Optional<String> documentedDefaultValue() public Optional<String> documentedDefaultValue()
{ {
Expand Down
Expand Up @@ -1227,6 +1227,12 @@ public boolean internal()
return newSetting.internal(); return newSetting.internal();
} }


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

@Override @Override
public Optional<String> documentedDefaultValue() public Optional<String> documentedDefaultValue()
{ {
Expand Down
Expand Up @@ -157,6 +157,12 @@ public boolean internal()
return false; return false;
} }


@Override
public boolean isSecret()
{
return false;
}

@Override @Override
public Optional<String> documentedDefaultValue() public Optional<String> documentedDefaultValue()
{ {
Expand Down
Expand Up @@ -39,6 +39,7 @@
import org.neo4j.configuration.Internal; import org.neo4j.configuration.Internal;
import org.neo4j.configuration.LoadableConfig; import org.neo4j.configuration.LoadableConfig;
import org.neo4j.configuration.ReplacedBy; import org.neo4j.configuration.ReplacedBy;
import org.neo4j.configuration.Secret;
import org.neo4j.graphdb.config.InvalidSettingException; import org.neo4j.graphdb.config.InvalidSettingException;
import org.neo4j.graphdb.config.Setting; import org.neo4j.graphdb.config.Setting;
import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.graphdb.factory.GraphDatabaseSettings;
Expand Down Expand Up @@ -101,6 +102,9 @@ public static class MySettingsWithDefaults implements LoadableConfig


@Deprecated @Deprecated
public static final Setting<String> oldSetting = setting( "some_setting", STRING, "Has no replacement" ); public static final Setting<String> oldSetting = setting( "some_setting", STRING, "Has no replacement" );

@Secret
public static final Setting<String> password = setting( "password", STRING, "This text should not appear in logs or toString" );
} }


private static class HelloHasToBeNeo4jConfigurationValidator implements ConfigurationValidator private static class HelloHasToBeNeo4jConfigurationValidator implements ConfigurationValidator
Expand Down Expand Up @@ -249,16 +253,35 @@ public void shouldSetInternalParameter()
{ {
// Given // Given
Config config = Config.builder() Config config = Config.builder()
.withSetting( MySettingsWithDefaults.secretSetting, "false" ) .withSetting( MySettingsWithDefaults.secretSetting, "false" )
.withSetting( MySettingsWithDefaults.hello, "ABC" ) .withSetting( MySettingsWithDefaults.hello, "ABC" )
.withConfigClasses( Arrays.asList( mySettingsWithDefaults, myMigratingSettings ) ) .withConfigClasses( Arrays.asList( mySettingsWithDefaults, myMigratingSettings ) )
.build(); .build();


// Then // Then
assertTrue( config.getConfigValues().get( MySettingsWithDefaults.secretSetting.name() ).internal() ); assertTrue( config.getConfigValues().get( MySettingsWithDefaults.secretSetting.name() ).internal() );
assertFalse( config.getConfigValues().get( MySettingsWithDefaults.hello.name() ).internal() ); assertFalse( config.getConfigValues().get( MySettingsWithDefaults.hello.name() ).internal() );
} }


@Test
public void shouldSetSecretParameter()
{
// Given
Config config = Config.builder()
.withSetting( MySettingsWithDefaults.password, "this should not be visible" )
.withSetting( MySettingsWithDefaults.hello, "ABC" )
.withConfigClasses( Arrays.asList( mySettingsWithDefaults, myMigratingSettings ) )
.build();

// Then
assertTrue( config.getConfigValues().get( MySettingsWithDefaults.password.name() ).isSecret() );
assertFalse( config.getConfigValues().get( MySettingsWithDefaults.hello.name() ).isSecret() );
String configText = config.toString();
assertTrue( configText.contains( Secret.OBSFUCATED ) );
assertFalse( configText.contains( "this should not be visible" ) );
assertFalse( configText.contains( config.get( MySettingsWithDefaults.password ) ) );
}

@Test @Test
public void shouldSetDocumentedDefaultValue() public void shouldSetDocumentedDefaultValue()
throws Exception throws Exception
Expand Down
Expand Up @@ -27,6 +27,7 @@
import org.neo4j.configuration.Description; import org.neo4j.configuration.Description;
import org.neo4j.configuration.Internal; import org.neo4j.configuration.Internal;
import org.neo4j.configuration.LoadableConfig; import org.neo4j.configuration.LoadableConfig;
import org.neo4j.configuration.Secret;
import org.neo4j.graphdb.config.Setting; import org.neo4j.graphdb.config.Setting;
import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.logging.Level; import org.neo4j.logging.Level;
Expand Down Expand Up @@ -225,6 +226,7 @@ public class SecuritySettings implements LoadableConfig
public static final Setting<String> ldap_authorization_system_username = public static final Setting<String> ldap_authorization_system_username =
setting( "dbms.security.ldap.authorization.system_username", STRING, NO_DEFAULT ); setting( "dbms.security.ldap.authorization.system_username", STRING, NO_DEFAULT );


@Secret
@Description( @Description(
"An LDAP system account password to use for authorization searches when " + "An LDAP system account password to use for authorization searches when " +
"`dbms.security.ldap.authorization.use_system_account` is `true`." ) "`dbms.security.ldap.authorization.use_system_account` is `true`." )
Expand Down

0 comments on commit 382113f

Please sign in to comment.