org.neo4j.bolt.BoltKernelExtension.java Source code

Java tutorial

Introduction

Here is the source code for org.neo4j.bolt.BoltKernelExtension.java

Source

/*
 * Copyright (c) 2002-2016 "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.bolt;

import io.netty.channel.Channel;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import org.bouncycastle.operator.OperatorCreationException;

import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.time.Clock;
import java.util.List;
import java.util.function.BiFunction;

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.Session;
import org.neo4j.bolt.v1.runtime.Sessions;
import org.neo4j.bolt.v1.runtime.internal.EncryptionRequiredSessions;
import org.neo4j.bolt.v1.runtime.internal.StandardSessions;
import org.neo4j.bolt.v1.runtime.internal.concurrent.ThreadedSessions;
import org.neo4j.bolt.v1.transport.BoltProtocolV1;
import org.neo4j.collection.primitive.PrimitiveLongObjectMap;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.config.Configuration;
import org.neo4j.graphdb.config.Setting;
import org.neo4j.graphdb.factory.Description;
import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.graphdb.factory.GraphDatabaseSettings.BoltConnector;
import org.neo4j.helpers.HostnamePort;
import org.neo4j.helpers.Service;
import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.configuration.Internal;
import org.neo4j.kernel.extension.KernelExtensionFactory;
import org.neo4j.kernel.impl.core.ThreadToStatementContextBridge;
import org.neo4j.kernel.impl.factory.GraphDatabaseFacade;
import org.neo4j.kernel.impl.logging.LogService;
import org.neo4j.kernel.impl.spi.KernelContext;
import org.neo4j.kernel.impl.util.JobScheduler;
import org.neo4j.kernel.lifecycle.LifeSupport;
import org.neo4j.kernel.lifecycle.Lifecycle;
import org.neo4j.kernel.monitoring.Monitors;
import org.neo4j.logging.Log;
import org.neo4j.udc.UsageData;

import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
import static org.neo4j.collection.primitive.Primitive.longObjectMap;
import static org.neo4j.graphdb.factory.GraphDatabaseSettings.Connector.ConnectorType.BOLT;
import static org.neo4j.kernel.configuration.GroupSettingSupport.enumerate;
import static org.neo4j.kernel.configuration.Settings.PATH;
import static org.neo4j.kernel.configuration.Settings.derivedSetting;
import static org.neo4j.kernel.configuration.Settings.pathSetting;
import static org.neo4j.kernel.impl.util.JobScheduler.Groups.boltNetworkIO;

/**
 * Wraps Bolt and exposes it as a Kernel Extension.
 */
@Service.Implementation(KernelExtensionFactory.class)
public class BoltKernelExtension extends KernelExtensionFactory<BoltKernelExtension.Dependencies> {
    public static class Settings {
        @Description("Directory for storing certificates to be used by Neo4j for TLS connections")
        public static Setting<File> certificates_directory = pathSetting("dbms.directories.certificates",
                "certificates");

        @Internal
        @Description("Path to the X.509 public certificate to be used by Neo4j for TLS connections")
        public static Setting<File> tls_certificate_file = derivedSetting(
                "unsupported.dbms.security.tls_certificate_file", certificates_directory,
                (certificates) -> new File(certificates, "neo4j.cert"), PATH);

        @Internal
        @Description("Path to the X.509 private key to be used by Neo4j for TLS connections")
        public static final Setting<File> tls_key_file = derivedSetting("unsupported.dbms.security.tls_key_file",
                certificates_directory, (certificates) -> new File(certificates, "neo4j.key"), PATH);
    }

    public interface Dependencies {
        LogService logService();

        Config config();

        GraphDatabaseService db();

        JobScheduler scheduler();

        UsageData usageData();

        Monitors monitors();

        ThreadToStatementContextBridge txBridge();
    }

    public BoltKernelExtension() {
        super("bolt-server");
    }

    @Override
    public Lifecycle newInstance(KernelContext context, Dependencies dependencies) throws Throwable {
        final Config config = dependencies.config();
        final GraphDatabaseService gdb = dependencies.db();
        final GraphDatabaseFacade api = (GraphDatabaseFacade) gdb;
        final LogService logging = dependencies.logService();
        final Log log = logging.getInternalLog(Sessions.class);

        final LifeSupport life = new LifeSupport();

        final JobScheduler scheduler = dependencies.scheduler();

        Netty4LogBridge.setLogProvider(logging.getInternalLogProvider());

        Sessions sessions = new MonitoredSessions(dependencies.monitors(),
                new ThreadedSessions(life
                        .add(new StandardSessions(api, dependencies.usageData(), logging, dependencies.txBridge())),
                        scheduler, logging),
                Clock.systemUTC());

        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) -> {
                    HostnamePort address = config.get(connConfig.address);
                    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, address);
                        break;
                    default:
                        // case DISABLED:
                        sslCtx = null;
                        break;
                    }

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

        if (connectors.size() > 0 && !config.get(GraphDatabaseSettings.disconnected)) {
            life.add(new NettyServer(scheduler.threadFactory(boltNetworkIO), connectors));
            log.info("Bolt Server extension loaded.");
            for (ProtocolInitializer connector : connectors) {
                logging.getUserLog(Sessions.class).info("Bolt enabled on %s.", connector.address());
            }
        }

        return life;
    }

    private SslContext createSslContext(Config config, Log log, HostnamePort address) {
        try {
            KeyStoreInformation keyStore = createKeyStore(config, log, address);
            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) {
        PrimitiveLongObjectMap<BiFunction<Channel, Boolean, BoltProtocol>> availableVersions = longObjectMap();
        availableVersions.put(BoltProtocolV1.VERSION, (channel, isEncrypted) -> {
            String descriptor = format("\tclient%s\tserver%s", channel.remoteAddress(), channel.localAddress());
            Session session = sessions.newSession(descriptor, isEncrypted);
            return new BoltProtocolV1(session, channel, logging);
        });
        return availableVersions;
    }

    private KeyStoreInformation createKeyStore(Configuration config, Log log, HostnamePort address)
            throws GeneralSecurityException, IOException, OperatorCreationException {
        File privateKeyPath = config.get(Settings.tls_key_file).getAbsoluteFile();
        File certificatePath = config.get(Settings.tls_certificate_file).getAbsoluteFile();

        if ((!certificatePath.exists() && !privateKeyPath.exists())) {
            log.info("No SSL certificate found, generating a self-signed certificate..");
            Certificates certFactory = new Certificates();
            certFactory.createSelfSignedCertificate(certificatePath, privateKeyPath, address.getHost());
        }

        if (!certificatePath.exists()) {
            throw new IllegalStateException(
                    format("TLS private key found, but missing certificate at '%s'. Cannot start server without "
                            + "certificate.", certificatePath));
        }
        if (!privateKeyPath.exists()) {
            throw new IllegalStateException(
                    format("TLS certificate found, but missing key at '%s'. Cannot start server without key.",
                            privateKeyPath));
        }

        return new KeyStoreFactory().createKeyStore(privateKeyPath, certificatePath);
    }
}