Skip to content

Commit

Permalink
Move Bolt connector config to public config API
Browse files Browse the repository at this point in the history
- Refactor the 'connector' configuration to achieve three things:

  - Change the API approach to a design that allows deducing the
    group configuration through introspection, allowing auto-docs
    for these settings
  - Move the Bolt connector configuration to GDS, because we want
    it to be publicly documented and because we want users of GDS
    to be able to enable Bolt in order to migrate existing GDS
    apps gradually.
  - Split the Connector group into a general `Connector` with
    the key, `enabled` and `type`. This allows Connector to be
    expanded as a general configuration API for configuring
    public ports (eg. have HTTP connector, Shell connector, etc etc).
    As per discussion with operability team (by which I mean I had
    a phone call with Ben)
  • Loading branch information
jakewins committed Mar 10, 2016
1 parent 0bd940a commit 05249fc
Show file tree
Hide file tree
Showing 15 changed files with 423 additions and 158 deletions.
153 changes: 51 additions & 102 deletions community/bolt/src/main/java/org/neo4j/bolt/BoltKernelExtension.java
Expand Up @@ -28,17 +28,16 @@
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.time.Clock;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;

import org.neo4j.bolt.security.ssl.Certificates;
import org.neo4j.bolt.security.ssl.KeyStoreFactory;
import org.neo4j.bolt.security.ssl.KeyStoreInformation;
import org.neo4j.bolt.transport.BoltProtocol;
import org.neo4j.bolt.transport.Netty4LogBridge;
import org.neo4j.bolt.transport.NettyServer;
import org.neo4j.bolt.transport.NettyServer.ProtocolInitializer;
import org.neo4j.bolt.transport.SocketTransport;
import org.neo4j.bolt.v1.runtime.MonitoredSessions;
import org.neo4j.bolt.v1.runtime.Sessions;
Expand All @@ -51,11 +50,10 @@
import org.neo4j.graphdb.config.Configuration;
import org.neo4j.graphdb.config.Setting;
import org.neo4j.graphdb.factory.Description;
import org.neo4j.helpers.HostnamePort;
import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.graphdb.factory.GraphDatabaseSettings.BoltConnector;
import org.neo4j.helpers.Service;
import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.configuration.ConfigGroups;
import org.neo4j.kernel.configuration.ConfigValues;
import org.neo4j.kernel.extension.KernelExtensionFactory;
import org.neo4j.kernel.impl.core.ThreadToStatementContextBridge;
import org.neo4j.kernel.impl.factory.GraphDatabaseFacade;
Expand All @@ -68,15 +66,13 @@
import org.neo4j.logging.Log;
import org.neo4j.udc.UsageData;

import static org.neo4j.bolt.BoltKernelExtension.EncryptionLevel.OPTIONAL;
import static java.util.stream.Collectors.toList;
import static org.neo4j.collection.primitive.Primitive.longObjectMap;
import static org.neo4j.kernel.configuration.GroupSettingSupport.enumerate;
import static org.neo4j.kernel.configuration.Settings.ANY;
import static org.neo4j.kernel.configuration.Settings.BOOLEAN;
import static org.neo4j.kernel.configuration.Settings.HOSTNAME_PORT;
import static org.neo4j.kernel.configuration.Settings.PATH;
import static org.neo4j.kernel.configuration.Settings.STRING;
import static org.neo4j.kernel.configuration.Settings.illegalValueMessage;
import static org.neo4j.kernel.configuration.Settings.options;
import static org.neo4j.kernel.configuration.Settings.setting;
import static org.neo4j.kernel.impl.util.JobScheduler.Groups.boltNetworkIO;

Expand All @@ -88,20 +84,6 @@ public class BoltKernelExtension extends KernelExtensionFactory<BoltKernelExtens
{
public static class Settings
{
public static final Function<ConfigValues,List<Configuration>> connector_group =
ConfigGroups.groups( "dbms.connector" );

@Description( "Enable Neo4j Bolt" )
public static final Setting<Boolean> enabled = setting( "enabled", BOOLEAN, "false" );

@Description( "Set the encryption level for Neo4j Bolt protocol ports" )
public static final Setting<EncryptionLevel> tls_level =
setting( "tls_level", options( EncryptionLevel.class ), OPTIONAL.name() );

@Description( "Host and port for the Neo4j Bolt Protocol" )
public static final Setting<HostnamePort> socket_address =
setting( "address", HOSTNAME_PORT, "localhost:7687" );

@Description( "Path to the X.509 public certificate to be used by Neo4j for TLS connections" )
public static Setting<File> tls_certificate_file = setting(
"dbms.security.tls_certificate_file", PATH, "neo4j-home/ssl/snakeoil.cert" );
Expand All @@ -115,51 +97,6 @@ public static class Settings
setting( "org.neo4j.server.webserver.address", STRING,
"localhost", illegalValueMessage( "Must be a valid hostname", org.neo4j.kernel.configuration
.Settings.matches( ANY ) ) );


public static <T> Setting<T> connector( int i, Setting<T> setting )
{
String name = String.format( "dbms.connector.%s", i );
return new Setting<T>()
{
@Override
public String name()
{
return String.format( "%s.%s", name, setting.name() );
}

@Override
public String getDefaultValue()
{
return setting.getDefaultValue();
}

@Override
public T apply( Function<String,String> settings )
{
return setting.apply( settings );
}

@Override
public int hashCode()
{
return name().hashCode();
}

@Override
public boolean equals( Object obj )
{
return obj instanceof Setting<?> && ((Setting<?>) obj).name().equals( name() );
}
};
}
}

public enum EncryptionLevel
{
REQUIRED,
OPTIONAL,
DISABLED
}

public interface Dependencies
Expand Down Expand Up @@ -206,40 +143,36 @@ public Lifecycle newInstance( KernelContext context, Dependencies dependencies )
dependencies.txBridge() ) ),
scheduler, logging ), Clock.systemUTC() );

List<NettyServer.ProtocolInitializer> connectors = new ArrayList<>();

List<Configuration> view = config.view( Settings.connector_group );
for ( Configuration connector : view )
{
final HostnamePort socketAddress = connector.get( Settings.socket_address );

if ( connector.get( Settings.enabled ) )
{
SslContext sslCtx;
boolean requireEncryption = false;
switch ( connector.get( Settings.tls_level ) )
{
// self signed cert should be generated when encryption is REQUIRED or OPTIONAL on the server
// while no cert is generated if encryption is DISABLED
case REQUIRED:
requireEncryption = true;
// no break here
case OPTIONAL:
KeyStoreInformation keyStore = createKeyStore( config, log );
sslCtx = SslContextBuilder.forServer( keyStore.getCertificatePath(), keyStore.getPrivateKeyPath() )
.build();
break;
default:
// case DISABLED:
sslCtx = null;
break;
}

connectors.add( new SocketTransport( socketAddress, sslCtx, logging.getInternalLogProvider(),
newVersions( logging,
requireEncryption ? new EncryptionRequiredSessions( sessions ) : sessions) ) );
}
}
List<ProtocolInitializer> connectors = config
.view( enumerate( GraphDatabaseSettings.Connector.class ) )
.map( BoltConnector::new )
.filter( (connConfig) -> "bolt".equals(config.get( connConfig.type ))
&& config.get( connConfig.enabled ) )
.map( (connConfig) -> {
SslContext sslCtx;
boolean requireEncryption = false;
switch ( config.get( connConfig.encryption_level ) )
{
// self signed cert should be generated when encryption is REQUIRED or OPTIONAL on the server
// while no cert is generated if encryption is DISABLED
case REQUIRED:
requireEncryption = true;
// no break here
case OPTIONAL:
sslCtx = createSslContext( config, log );
break;
default:
// case DISABLED:
sslCtx = null;
break;
}

return new SocketTransport( config.get( connConfig.address ),
sslCtx, logging.getInternalLogProvider(),
newVersions( logging, requireEncryption ?
new EncryptionRequiredSessions( sessions ) : sessions) );
})
.collect( toList() );

if ( connectors.size() > 0 )
{
Expand All @@ -250,6 +183,22 @@ public Lifecycle newInstance( KernelContext context, Dependencies dependencies )
return life;
}

private SslContext createSslContext( Config config, Log log )
{
try
{
KeyStoreInformation keyStore = createKeyStore( config, log );
return SslContextBuilder
.forServer( keyStore.getCertificatePath(), keyStore.getPrivateKeyPath() )
.build();
}
catch(IOException | OperatorCreationException | GeneralSecurityException e )
{
throw new RuntimeException( "Failed to initilize SSL encryption support, which is required to start this " +
"connector. Error was: " + e.getMessage(), e );
}
}

private PrimitiveLongObjectMap<BiFunction<Channel,Boolean,BoltProtocol>> newVersions( LogService logging,
Sessions sessions )
{
Expand Down
Expand Up @@ -22,7 +22,6 @@
import org.junit.Rule;
import org.junit.Test;

import org.neo4j.bolt.BoltKernelExtension;
import org.neo4j.bolt.v1.transport.integration.Neo4jWithSocket;
import org.neo4j.bolt.v1.transport.integration.TransportTestUtil;
import org.neo4j.bolt.v1.transport.socket.client.Connection;
Expand All @@ -35,23 +34,23 @@

import static java.util.Collections.emptyMap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.neo4j.bolt.BoltKernelExtension.EncryptionLevel.REQUIRED;
import static org.neo4j.bolt.BoltKernelExtension.Settings.connector;
import static org.neo4j.bolt.v1.messaging.message.Messages.init;
import static org.neo4j.bolt.v1.messaging.util.MessageMatchers.msgFailure;
import static org.neo4j.bolt.v1.transport.integration.TransportTestUtil.eventuallyRecieves;
import static org.neo4j.graphdb.factory.GraphDatabaseSettings.BoltConnector.EncryptionLevel.REQUIRED;
import static org.neo4j.graphdb.factory.GraphDatabaseSettings.boltConnector;

public class BoltConfigIT
{

@Rule
public Neo4jWithSocket server = new Neo4jWithSocket(
settings -> {
settings.put( connector(0, BoltKernelExtension.Settings.enabled), "true");
settings.put( connector(0, BoltKernelExtension.Settings.socket_address), "localhost:7888");
settings.put( connector(1, BoltKernelExtension.Settings.enabled), "true");
settings.put( connector(1, BoltKernelExtension.Settings.socket_address), "localhost:7687");
settings.put( connector(1, BoltKernelExtension.Settings.tls_level), REQUIRED.name() );
settings.put( boltConnector("0").enabled, "true" );
settings.put( boltConnector("0").address, "localhost:7888" );
settings.put( boltConnector("1").enabled, "true" );
settings.put( boltConnector("1").address, "localhost:7687" );
settings.put( boltConnector("1").encryption_level, REQUIRED.name() );
} );

@Test
Expand Down
Expand Up @@ -38,8 +38,8 @@
import org.neo4j.kernel.impl.store.StoreId;
import org.neo4j.test.TestGraphDatabaseFactory;

import static org.neo4j.bolt.BoltKernelExtension.EncryptionLevel.OPTIONAL;
import static org.neo4j.bolt.BoltKernelExtension.Settings.connector;
import static org.neo4j.graphdb.factory.GraphDatabaseSettings.BoltConnector.EncryptionLevel.OPTIONAL;
import static org.neo4j.graphdb.factory.GraphDatabaseSettings.boltConnector;

public class Neo4jWithSocket implements TestRule
{
Expand Down Expand Up @@ -71,8 +71,8 @@ public Statement apply( final Statement statement, Description description )
public void evaluate() throws Throwable
{
Map<Setting<?>, String> settings = new HashMap<>();
settings.put( connector( 0, BoltKernelExtension.Settings.enabled ), "true" );
settings.put( connector( 0, BoltKernelExtension.Settings.tls_level ), OPTIONAL.name() );
settings.put( boltConnector( "0" ).enabled, "true" );
settings.put( boltConnector( "0" ).encryption_level, OPTIONAL.name() );
settings.put( BoltKernelExtension.Settings.tls_key_file, tempPath( "key", ".key" ) );
settings.put( BoltKernelExtension.Settings.tls_certificate_file, tempPath( "cert", ".cert" ) );
configure.accept( settings );
Expand Down
Expand Up @@ -30,23 +30,22 @@
import java.io.IOException;
import java.util.Collection;

import org.neo4j.bolt.BoltKernelExtension;
import org.neo4j.bolt.v1.transport.socket.client.Connection;
import org.neo4j.bolt.v1.transport.socket.client.SecureSocketConnection;
import org.neo4j.bolt.v1.transport.socket.client.SecureWebSocketConnection;
import org.neo4j.function.Factory;
import org.neo4j.helpers.HostnamePort;

import static java.util.Arrays.asList;
import static org.neo4j.bolt.BoltKernelExtension.EncryptionLevel.DISABLED;
import static org.neo4j.bolt.BoltKernelExtension.Settings.connector;
import static org.neo4j.graphdb.factory.GraphDatabaseSettings.BoltConnector.EncryptionLevel.DISABLED;
import static org.neo4j.graphdb.factory.GraphDatabaseSettings.boltConnector;

@RunWith( Parameterized.class )
public class RejectTransportEncryptionIT
{
@Rule
public Neo4jWithSocket server = new Neo4jWithSocket(
settings -> settings.put( connector( 0, BoltKernelExtension.Settings.tls_level ), DISABLED.name() ) );
settings -> settings.put( boltConnector( "0" ).encryption_level, DISABLED.name() ) );
@Rule
public ExpectedException exception = ExpectedException.none();

Expand Down
Expand Up @@ -28,7 +28,6 @@

import java.util.Collection;

import org.neo4j.bolt.BoltKernelExtension;
import org.neo4j.bolt.v1.transport.socket.client.Connection;
import org.neo4j.bolt.v1.transport.socket.client.SocketConnection;
import org.neo4j.bolt.v1.transport.socket.client.WebSocketConnection;
Expand All @@ -39,18 +38,18 @@
import static java.util.Arrays.asList;
import static java.util.Collections.emptyMap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.neo4j.bolt.BoltKernelExtension.EncryptionLevel.REQUIRED;
import static org.neo4j.bolt.BoltKernelExtension.Settings.connector;
import static org.neo4j.bolt.v1.messaging.message.Messages.init;
import static org.neo4j.bolt.v1.messaging.util.MessageMatchers.msgFailure;
import static org.neo4j.bolt.v1.transport.integration.TransportTestUtil.eventuallyRecieves;
import static org.neo4j.graphdb.factory.GraphDatabaseSettings.BoltConnector.EncryptionLevel.REQUIRED;
import static org.neo4j.graphdb.factory.GraphDatabaseSettings.boltConnector;

@RunWith( Parameterized.class )
public class RequiredTransportEncryptionIT
{
@Rule
public Neo4jWithSocket server = new Neo4jWithSocket(
settings -> settings.put( connector( 0, BoltKernelExtension.Settings.tls_level ), REQUIRED.name() ) );
settings -> settings.put( boltConnector( "0" ).encryption_level, REQUIRED.name() ) );

@Parameterized.Parameter( 0 )
public Factory<Connection> cf;
Expand Down

0 comments on commit 05249fc

Please sign in to comment.