org.apache.drill.cv.exec.server.rest.CvDrillWebServer.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.drill.cv.exec.server.rest.CvDrillWebServer.java

Source

/*
 <!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
  license agreements. See the NOTICE file distributed with this work for additional
  information regarding copyright ownership. The ASF licenses this file to
  You under the Apache License, Version 2.0 (the "License"); you may not use
  this file except in compliance with the License. You may obtain a copy of
  the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
  by applicable law or agreed to in writing, software distributed under the
  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
  OF ANY KIND, either express or implied. See the License for the specific
  language governing permissions and limitations under the License. -->
 */
package org.apache.drill.cv.exec.server.rest;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Date;
import java.util.Set;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.drill.common.config.DrillConfig;
import org.apache.drill.cv.exec.server.rest.auth.CvDrillRestLoginService;
import org.apache.drill.exec.ExecConstants;
import org.apache.drill.exec.server.rest.DrillRestServer;
import org.apache.drill.exec.server.rest.auth.DrillUserPrincipal;
import org.apache.drill.exec.work.WorkManager;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.security.authentication.SessionAuthentication;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SessionManager;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ShutdownHandler;
import org.eclipse.jetty.server.session.HashSessionManager;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.glassfish.jersey.servlet.ServletContainer;
import org.joda.time.DateTime;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.servlets.MetricsServlet;
import com.codahale.metrics.servlets.ThreadDumpServlet;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;

/**
 * Wrapper class around jetty based webserver.
 */
public class CvDrillWebServer implements AutoCloseable {

    private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CvDrillWebServer.class);
    private static final String BASE_STATIC_PATH = "/rest/static/";
    private static final String DRILL_ICON_RESOURCE_RELATIVE_PATH = "img/drill.ico";
    private final DrillConfig config;
    private final MetricRegistry metrics;
    private final WorkManager workManager;
    private final Server embeddedJetty;

    private String webserverPort;

    private String embeddedJettyStopKey = "defaultStopKey";

    /**
     * Create Jetty based web server.
     *
     * @param config
     *          DrillConfig instance.
     * @param metrics
     *          Metrics registry.
     * @param workManager
     *          WorkManager instance.
     */
    public CvDrillWebServer(final DrillConfig config, final MetricRegistry metrics, final WorkManager workManager) {
        this.config = config;
        this.metrics = metrics;
        this.workManager = workManager;

        if (config.getBoolean(ExecConstants.HTTP_ENABLE)) {
            embeddedJetty = new Server();
        } else {
            embeddedJetty = null;
        }
    }

    @Override
    public void close() throws Exception {
        if (embeddedJetty != null) {
            embeddedJetty.stop();
        }
    }

    /**
     * Create HTTP connector.
     *
     * @return Initialized {@link ServerConnector} instance for HTTP connections.
     * @throws Exception
     */
    private ServerConnector createHttpConnector() throws Exception {
        CvDrillWebServer.logger.info("Setting up HTTP connector for web server");
        final HttpConfiguration httpConfig = new HttpConfiguration();
        final ServerConnector httpConnector = new ServerConnector(embeddedJetty,
                new HttpConnectionFactory(httpConfig));
        httpConnector.setPort(getWebserverPort());

        return httpConnector;
    }

    /**
     * Create an HTTPS connector for given jetty server instance. If the admin has specified
     * keystore/truststore settings they will be used else a self-signed certificate is generated and
     * used.
     *
     * @return Initialized {@link ServerConnector} for HTTPS connectios.
     * @throws Exception
     */
    private ServerConnector createHttpsConnector() throws Exception {
        CvDrillWebServer.logger.info("Setting up HTTPS connector for web server");

        final SslContextFactory sslContextFactory = new SslContextFactory();

        if (config.hasPath(ExecConstants.HTTP_KEYSTORE_PATH)
                && !Strings.isNullOrEmpty(config.getString(ExecConstants.HTTP_KEYSTORE_PATH))) {
            CvDrillWebServer.logger.info("Using configured SSL settings for web server");
            sslContextFactory.setKeyStorePath(config.getString(ExecConstants.HTTP_KEYSTORE_PATH));
            sslContextFactory.setKeyStorePassword(config.getString(ExecConstants.HTTP_KEYSTORE_PASSWORD));

            // TrustStore and TrustStore password are optional
            if (config.hasPath(ExecConstants.HTTP_TRUSTSTORE_PATH)) {
                sslContextFactory.setTrustStorePath(config.getString(ExecConstants.HTTP_TRUSTSTORE_PATH));
                if (config.hasPath(ExecConstants.HTTP_TRUSTSTORE_PASSWORD)) {
                    sslContextFactory
                            .setTrustStorePassword(config.getString(ExecConstants.HTTP_TRUSTSTORE_PASSWORD));
                }
            }
        } else {
            CvDrillWebServer.logger.info("Using generated self-signed SSL settings for web server");
            final SecureRandom random = new SecureRandom();

            // Generate a private-public key pair
            final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(1024, random);
            final KeyPair keyPair = keyPairGenerator.generateKeyPair();

            final DateTime now = DateTime.now();

            // Create builder for certificate attributes
            final X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE)
                    .addRDN(BCStyle.OU, "Apache Drill (auth-generated)")
                    .addRDN(BCStyle.O, "Apache Software Foundation (auto-generated)")
                    .addRDN(BCStyle.CN, workManager.getContext().getEndpoint().getAddress());

            final Date notBefore = now.minusMinutes(1).toDate();
            final Date notAfter = now.plusYears(5).toDate();
            final BigInteger serialNumber = new BigInteger(128, random);

            // Create a certificate valid for 5years from now.
            final X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(nameBuilder.build(), // attributes
                    serialNumber, notBefore, notAfter, nameBuilder.build(), keyPair.getPublic());

            // Sign the certificate using the private key
            final ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
                    .build(keyPair.getPrivate());
            final X509Certificate certificate = new JcaX509CertificateConverter()
                    .getCertificate(certificateBuilder.build(contentSigner));

            // Check the validity
            certificate.checkValidity(now.toDate());

            // Make sure the certificate is self-signed.
            certificate.verify(certificate.getPublicKey());

            // Generate a random password for keystore protection
            final String keyStorePasswd = RandomStringUtils.random(20);
            final KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(null, null);
            keyStore.setKeyEntry("DrillAutoGeneratedCert", keyPair.getPrivate(), keyStorePasswd.toCharArray(),
                    new java.security.cert.Certificate[] { certificate });

            sslContextFactory.setKeyStore(keyStore);
            sslContextFactory.setKeyStorePassword(keyStorePasswd);
        }

        final HttpConfiguration httpsConfig = new HttpConfiguration();
        httpsConfig.addCustomizer(new SecureRequestCustomizer());

        // SSL Connector
        final ServerConnector sslConnector = new ServerConnector(embeddedJetty,
                new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
                new HttpConnectionFactory(httpsConfig));
        sslConnector.setPort(getWebserverPort());

        return sslConnector;
    }

    /**
     * @return {@link SecurityHandler} with appropriate {@link LoginService}, {@link Authenticator}
     *         and constraints.
     */
    private ConstraintSecurityHandler createSecurityHandler() {
        ConstraintSecurityHandler security = new ConstraintSecurityHandler();

        Set<String> knownRoles = ImmutableSet.of(DrillUserPrincipal.AUTHENTICATED_ROLE,
                DrillUserPrincipal.ADMIN_ROLE);
        security.setConstraintMappings(Collections.<ConstraintMapping>emptyList(), knownRoles);

        security.setAuthenticator(new FormAuthenticator("/login", "/login", true));
        security.setLoginService(new CvDrillRestLoginService(workManager.getContext()));

        return security;
    }

    /**
     * @return A {@link SessionHandler} which contains a {@link HashSessionManager}
     */
    private SessionHandler createSessionHandler(final SecurityHandler securityHandler) {
        SessionManager sessionManager = new HashSessionManager();
        sessionManager.setMaxInactiveInterval(config.getInt(ExecConstants.HTTP_SESSION_MAX_IDLE_SECS));
        sessionManager.addEventListener(new HttpSessionListener() {
            @Override
            public void sessionCreated(HttpSessionEvent se) {
                // No-op
            }

            @Override
            public void sessionDestroyed(HttpSessionEvent se) {
                final HttpSession session = se.getSession();
                if (session == null) {
                    return;
                }

                final Object authCreds = session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
                if (authCreds != null) {
                    final SessionAuthentication sessionAuth = (SessionAuthentication) authCreds;
                    securityHandler.logout(sessionAuth);
                    session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
                }
            }
        });

        return new SessionHandler(sessionManager);
    }

    public String getEmbeddedJettyStopKey() {
        return embeddedJettyStopKey;
    }

    private int getWebserverPort() {
        int port = -1;
        if (webserverPort != null) {
            port = Integer.parseInt(webserverPort);
        } else {
            port = config.getInt(ExecConstants.HTTP_PORT);
        }
        return port;
    }

    public void setEmbeddedJettyStopKey(String embeddedJettyStopKey) {
        this.embeddedJettyStopKey = embeddedJettyStopKey;
    }

    public void setWebserverPort(String webserverPort) {
        this.webserverPort = webserverPort;
    }

    /**
     * Start the web server including setup.
     *
     * @throws Exception
     */
    public void start() throws Exception {
        if (embeddedJetty == null) {
            return;
        }

        final ServerConnector serverConnector;
        if (config.getBoolean(ExecConstants.HTTP_ENABLE_SSL)) {
            serverConnector = createHttpsConnector();
        } else {
            serverConnector = createHttpConnector();
        }
        embeddedJetty.addConnector(serverConnector);

        // Add resources
        final ErrorHandler errorHandler = new ErrorHandler();
        errorHandler.setShowStacks(true);
        errorHandler.setShowMessageInTitle(true);

        final ServletContextHandler servletContextHandler = new ServletContextHandler(
                ServletContextHandler.SESSIONS);
        servletContextHandler.setErrorHandler(errorHandler);
        servletContextHandler.setContextPath("/");
        embeddedJetty.setHandler(servletContextHandler);

        final ServletHolder servletHolder = new ServletHolder(
                new ServletContainer(new DrillRestServer(workManager)));
        servletHolder.setInitOrder(1);
        servletContextHandler.addServlet(servletHolder, "/*");

        servletContextHandler.addServlet(new ServletHolder(new MetricsServlet(metrics)), "/status/metrics");
        servletContextHandler.addServlet(new ServletHolder(new ThreadDumpServlet()), "/status/threads");

        final ServletHolder staticHolder = new ServletHolder("static", DefaultServlet.class);
        // Get resource URL for Drill static assets, based on where Drill icon is located
        String drillIconResourcePath = Resource
                .newClassPathResource(
                        CvDrillWebServer.BASE_STATIC_PATH + CvDrillWebServer.DRILL_ICON_RESOURCE_RELATIVE_PATH)
                .getURL().toString();
        staticHolder.setInitParameter("resourceBase", drillIconResourcePath.substring(0,
                drillIconResourcePath.length() - CvDrillWebServer.DRILL_ICON_RESOURCE_RELATIVE_PATH.length()));
        staticHolder.setInitParameter("dirAllowed", "false");
        staticHolder.setInitParameter("pathInfoOnly", "true");
        servletContextHandler.addServlet(staticHolder, "/static/*");

        ShutdownHandler shutDwnHandler = new ShutdownHandler(getEmbeddedJettyStopKey(), false, false);
        HandlerList handlers = new HandlerList();
        servletContextHandler.setHandler(shutDwnHandler);
        handlers.addHandler(servletContextHandler);

        embeddedJetty.setHandler(handlers);

        if (config.getBoolean(ExecConstants.USER_AUTHENTICATION_ENABLED)) {
            servletContextHandler.setSecurityHandler(createSecurityHandler());
            servletContextHandler
                    .setSessionHandler(createSessionHandler(servletContextHandler.getSecurityHandler()));
        }
        embeddedJetty.start();
    }

}