org.italiangrid.voms.container.Container.java Source code

Java tutorial

Introduction

Here is the source code for org.italiangrid.voms.container.Container.java

Source

/**
 * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2006-2016
 *
 * Licensed 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.italiangrid.voms.container;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.jar.Attributes;
import java.util.jar.Attributes.Name;
import java.util.jar.JarFile;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
import org.eclipse.jetty.util.log.Log;
import org.italiangrid.utils.https.JettyRunThread;
import org.italiangrid.utils.https.SSLOptions;
import org.italiangrid.utils.https.ServerFactory;
import org.italiangrid.utils.https.impl.canl.CANLListener;
import org.italiangrid.voms.container.legacy.VOMSSslConnectorConfigurator;
import org.italiangrid.voms.container.lifecycle.ServerListener;
import org.italiangrid.voms.util.CertificateValidatorBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import eu.emi.security.authn.x509.CrlCheckingMode;
import eu.emi.security.authn.x509.OCSPCheckingMode;
import eu.emi.security.authn.x509.X509CertChainValidatorExt;

public class Container {

    public static final String LOGGER_CONTEXT_NAME = "voms-admin";

    public static final Logger log = LoggerFactory.getLogger(Container.class);

    private static final String TAGLIBS_JAR_NAME = "org.apache.taglibs.standard.glassfish";

    public static final String CONF_FILE_NAME = "voms-admin-server.properties";

    public static final String DEFAULT_WAR = "/usr/share/webapps/voms-admin.war";

    public static final String DEFAULT_TMP_PREFIX = "/var/tmp";
    public static final String DEFAULT_DEPLOY_DIR = "/var/lib/voms-admin/vo.d";
    public static final String DEFAULT_WORK_DIR = "/var/lib/voms-admin/work";

    public static final String HTTP_CONNECTOR_NAME = "voms-http";
    public static final String HTTPS_CONNECTOR_NAME = "voms-https";

    public static final String HTTP_CONNECTOR_PORT = "8088";

    private static final String ARG_WAR = "war";
    private static final String ARG_CONFDIR = "confdir";
    private static final String ARG_DEPLOYDIR = "deploydir";

    private static final String SERVICE_PROPERTIES_FILE = "service.properties";
    private static final String SERVICE_PORT_KEY = "voms.aa.x509.additional_port";

    private Options cliOptions;
    private CommandLineParser parser = new GnuParser();

    private Properties serverConfiguration;

    private String war;
    private String confDir;
    private String deployDir;
    private String workDir;

    private String host;
    private String port;
    private String statusPort;

    private String certFile;
    private String keyFile;
    private String trustDir;
    private long trustDirRefreshIntervalInMsec;

    private String bindAddress;

    private String tlsExcludeProtocols;
    private String tlsIncludeProtocols;
    private String tlsExcludeCipherSuites;
    private String tlsIncludeCipherSuites;
    private boolean tlsRequireClientCert = true;

    private Server server;
    private DeploymentManager deploymentManager;
    private HandlerCollection handlers = new HandlerCollection();
    private ContextHandlerCollection contexts = new ContextHandlerCollection();

    protected SSLOptions getSSLOptions() {

        SSLOptions options = new SSLOptions();

        options.setCertificateFile(certFile);
        options.setKeyFile(keyFile);
        options.setTrustStoreDirectory(trustDir);
        options.setTrustStoreRefreshIntervalInMsec(trustDirRefreshIntervalInMsec);

        options.setWantClientAuth(true);

        options.setNeedClientAuth(tlsRequireClientCert);

        if (tlsExcludeCipherSuites != null) {
            options.setExcludeCipherSuites(tlsExcludeCipherSuites.split(","));
        }

        if (tlsIncludeCipherSuites != null) {
            options.setIncludeCipherSuites(tlsIncludeCipherSuites.split(","));
        }

        if (tlsExcludeProtocols != null) {
            options.setExcludeProtocols(tlsExcludeProtocols.split(","));
        }

        if (tlsIncludeProtocols != null) {
            options.setIncludeProtocols(tlsIncludeProtocols.split(","));
        }

        return options;

    }

    protected void confDirSanityChecks() {

        File confDirFile = new File(confDir);

        if (!confDirFile.exists() || !confDirFile.isDirectory()) {

            throw new IllegalArgumentException("VOMS Admin configuration directory "
                    + "does not exists or is not a directory: " + confDirFile.getAbsolutePath());
        }
    }

    protected Properties getVOServiceProperties(String voName) {

        if (!getConfiguredVONames().contains(voName))
            throw new IllegalArgumentException("VO " + voName + " is " + "not configured on this host.");

        Properties voServiceProps = new Properties();

        String propsPath = String.format("%s/%s/%s", confDir, voName, SERVICE_PROPERTIES_FILE).replaceAll("/+",
                "/");

        try {
            voServiceProps.load(new FileInputStream(propsPath));
            return voServiceProps;

        } catch (IOException e) {
            log.error("Error reading service properties configuration for VO {}: {}.",
                    new Object[] { voName, e.getMessage() }, e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    protected Map<Integer, String> getConfiguredVOPortMappings() {

        Map<Integer, String> voPorts = new HashMap<Integer, String>();

        for (String vo : getConfiguredVONames()) {
            Properties sp = getVOServiceProperties(vo);
            if (sp.containsKey(SERVICE_PORT_KEY)) {
                int port = Integer.parseInt(sp.getProperty(SERVICE_PORT_KEY));
                if (port > 0)
                    voPorts.put(port, vo);
            }
        }

        return voPorts;
    }

    protected List<String> getConfiguredVONames() {

        confDirSanityChecks();

        List<String> voNames = new ArrayList<String>();

        File confDirFile = new File(confDir);

        File[] voFiles = confDirFile.listFiles(new FileFilter() {

            @Override
            public boolean accept(File pathname) {

                return pathname.isDirectory();
            }
        });

        for (File f : voFiles) {
            voNames.add(f.getName());
        }

        return voNames;
    }

    protected void configureDeploymentManager() {

        contexts.setServer(server);

        deploymentManager = new DeploymentManager();

        VOMSAppProvider provider = new VOMSAppProvider();

        provider.setConfigurationDir(confDir);
        provider.setDeploymentDir(deployDir);
        provider.setHostname(host);
        provider.setPort(port);
        provider.setWarFile(war);
        provider.setWorkDir(workDir);

        deploymentManager.addAppProvider(provider);
        deploymentManager.setContexts(contexts);

    }

    protected void addNameToHTTPSConnector() {

        for (Connector c : server.getConnectors()) {
            if (c.getPort() == Integer.parseInt(port)) {
                SslSelectChannelConnector conn = (SslSelectChannelConnector) c;
                conn.setName(HTTPS_CONNECTOR_NAME);
            }
        }

    }

    protected void configureLocalHTTPConnector() {

        SelectChannelConnector conn = new SelectChannelConnector();
        conn.setHost("localhost");
        conn.setPort(Integer.parseInt(statusPort));
        conn.setName(HTTP_CONNECTOR_NAME);
        server.addConnector(conn);
    }

    protected void configureLegacyConnectors(String host, X509CertChainValidatorExt validator, SSLOptions options,
            int maxConnections, int maxRequestQueueSize) {

        VOMSSslConnectorConfigurator configurator = new VOMSSslConnectorConfigurator(validator);

        Map<Integer, String> voMappings = getConfiguredVOPortMappings();

        for (Map.Entry<Integer, String> e : voMappings.entrySet()) {
            int voPort = e.getKey();

            Connector c = configurator.configureConnector(host, voPort, options);

            ((SslSelectChannelConnector) c).setName("voms-" + e.getValue());

            server.addConnector(c);
            log.info("Configured VOMS legacy connector for VO {} on port {}.", e.getValue(), voPort);
        }

    }

    protected void configureJettyServer() {

        SSLOptions options = getSSLOptions();

        CANLListener l = new CANLListener();

        CertificateValidatorBuilder builder = new CertificateValidatorBuilder();

        X509CertChainValidatorExt validator = builder.trustAnchorsDir(options.getTrustStoreDirectory())
                .trustAnchorsUpdateInterval(trustDirRefreshIntervalInMsec).crlChecks(CrlCheckingMode.IF_VALID)
                .lazyAnchorsLoading(false).ocspChecks(OCSPCheckingMode.IGNORE).storeUpdateListener(l)
                .validationErrorListener(l).build();

        int maxConnections = Integer.parseInt(getConfigurationProperty(ConfigurationProperty.MAX_CONNECTIONS));

        int maxRequestQueueSize = Integer
                .parseInt(getConfigurationProperty(ConfigurationProperty.MAX_REQUEST_QUEUE_SIZE));

        server = ServerFactory.newServer(bindAddress, Integer.parseInt(port), getSSLOptions(), validator,
                maxConnections, maxRequestQueueSize);

        MBeanContainer mbContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());

        // Enable JMX
        server.addBean(mbContainer);
        server.getContainer().addEventListener(mbContainer);
        mbContainer.addBean(Log.getLog());

        addNameToHTTPSConnector();
        configureLocalHTTPConnector();

        server.addLifeCycleListener(new ServerListener());

        configureDeploymentManager();

        configureLegacyConnectors(null, validator, options, maxConnections, maxRequestQueueSize);

        // Handlers contains VOMS webapp, status webapp, and as final
        // choice the default handler to correctly handle 404 for non-handled
        // contexts etc.
        handlers.setHandlers(new Handler[] { contexts, new DefaultHandler() });

        // The rewrite handler, will internally foward
        // generate-ac request to legacy voms ports to the appropriate
        // voms-admin webapp
        RewriteHandler rh = new RewriteHandler();
        rh.setRewritePathInfo(true);
        rh.setRewriteRequestURI(true);

        rh.addRule(new VOMSRewriteRule(getConfiguredVOPortMappings()));

        // webapps are wrapped in the rewrite handler
        // or VOMS port rewriting will not work
        rh.setHandler(handlers);
        server.setHandler(rh);

        server.setDumpAfterStart(false);
        server.setDumpBeforeStop(false);
        server.setStopAtShutdown(true);

        server.addBean(deploymentManager);

    }

    private void start() {

        JettyRunThread vomsService = new JettyRunThread(server);
        vomsService.start();
    }

    private void initOptions() {

        cliOptions = new Options();

        cliOptions.addOption(ARG_WAR, true, "The WAR used to start this server.");

        cliOptions.addOption(ARG_DEPLOYDIR, true, "The VOMS Admin deploy directory.");

        cliOptions.addOption(ARG_CONFDIR, true,
                "The configuration directory where " + "VOMS configuration is stored.");

    }

    private void failAndExit(String errorMessage, Throwable t) {

        if (t != null) {

            System.err.format("%s: %s\n", errorMessage, t.getMessage());

        } else {

            System.err.println(errorMessage);

        }

        System.exit(1);

    }

    private void parseCommandLineOptions(String[] args) {

        try {

            CommandLine cmdLine = parser.parse(cliOptions, args);

            Properties sysconfigProperties = SysconfigUtil.loadSysconfig();
            String installationPrefix = SysconfigUtil.getInstallationPrefix();

            String defaultPrefixedWarPath = String.format("%s/%s", installationPrefix, DEFAULT_WAR).replaceAll("/+",
                    "/");

            war = cmdLine.getOptionValue(ARG_WAR, defaultPrefixedWarPath);

            confDir = cmdLine.getOptionValue(ARG_CONFDIR);

            if (confDir == null) {
                confDir = sysconfigProperties.getProperty(SysconfigUtil.SYSCONFIG_CONF_DIR);
            }

            deployDir = cmdLine.getOptionValue(ARG_DEPLOYDIR);

        } catch (ParseException e) {

            failAndExit("Error parsing command line arguments", e);

        }
    }

    private String getConfigurationProperty(ConfigurationProperty prop) {

        return serverConfiguration.getProperty(prop.getPropertyName(), prop.getDefaultValue());
    }

    private void loadServerConfiguration(String configurationDir) {

        try {
            serverConfiguration = new Properties();

            String configurationPath = String.format("%s/%s", configurationDir, CONF_FILE_NAME).replaceAll("/+",
                    "/");

            serverConfiguration.load(new FileReader(configurationPath));

        } catch (IOException e) {
            throw new RuntimeException("Error loading configuration:" + e.getMessage(), e);
        }

    }

    private void loadConfiguration() {

        // FIXME: move this to standard server conf?
        // Then it would be harder to source from the init
        // script, which is the main (and only) client of
        // the status handler
        statusPort = SysconfigUtil.loadSysconfig().getProperty(SysconfigUtil.SYSCONFIG_STATUS_PORT,
                HTTP_CONNECTOR_PORT);

        loadServerConfiguration(confDir);

        host = getConfigurationProperty(ConfigurationProperty.HOST);
        port = getConfigurationProperty(ConfigurationProperty.PORT);

        bindAddress = getConfigurationProperty(ConfigurationProperty.BIND_ADDRESS);

        certFile = getConfigurationProperty(ConfigurationProperty.CERT);
        keyFile = getConfigurationProperty(ConfigurationProperty.KEY);

        trustDir = getConfigurationProperty(ConfigurationProperty.TRUST_ANCHORS_DIR);

        tlsIncludeCipherSuites = getConfigurationProperty(ConfigurationProperty.TLS_INCLUDE_CIPHER_SUITES);

        tlsExcludeCipherSuites = getConfigurationProperty(ConfigurationProperty.TLS_EXCLUDE_CIPHER_SUITES);

        tlsExcludeProtocols = getConfigurationProperty(ConfigurationProperty.TLS_EXCLUDE_PROTOCOLS);

        tlsIncludeProtocols = getConfigurationProperty(ConfigurationProperty.TLS_INCLUDE_PROTOCOLS);

        tlsRequireClientCert = Boolean
                .parseBoolean(getConfigurationProperty(ConfigurationProperty.TLS_REQUIRE_CLIENT_CERTIFICATE));

        long refreshIntervalInSeconds = Long
                .parseLong(getConfigurationProperty(ConfigurationProperty.TRUST_ANCHORS_REFRESH_PERIOD));

        trustDirRefreshIntervalInMsec = TimeUnit.SECONDS.toMillis(refreshIntervalInSeconds);

        if (deployDir == null) {
            deployDir = String.format("%s/%s", SysconfigUtil.getInstallationPrefix(), DEFAULT_DEPLOY_DIR)
                    .replaceAll("/+", "/");
        }

        workDir = String.format("%s/%s", SysconfigUtil.getInstallationPrefix(), DEFAULT_WORK_DIR).replaceAll("/+",
                "/");

    }

    // Without this trick JSP page rendering on VOMS admin does not work
    private void forceTaglibsLoading() {

        if (System.getProperty("voms.disableTaglibsLoading") != null) {
            log.warn("Taglibs loading disabled, as requested by voms.disableTaglibsLoading");
            return;
        }

        try {

            String classpath = java.lang.System.getProperty("java.class.path");
            String entries[] = classpath.split(System.getProperty("path.separator"));

            if (entries.length >= 1) {

                JarFile f = new JarFile(entries[0]);
                Attributes attrs = f.getManifest().getMainAttributes();
                Name n = new Name("Class-Path");
                String jarClasspath = attrs.getValue(n);
                String jarEntries[] = jarClasspath.split(" ");

                boolean taglibsFound = false;

                for (String e : jarEntries) {
                    if (e.contains(TAGLIBS_JAR_NAME)) {
                        taglibsFound = true;
                        ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();

                        File taglibsJar = new File(e);
                        URLClassLoader newClassLoader = new URLClassLoader(new URL[] { taglibsJar.toURI().toURL() },
                                currentClassLoader);

                        Thread.currentThread().setContextClassLoader(newClassLoader);
                    }
                }

                f.close();

                if (!taglibsFound) {
                    throw new RuntimeException("Error configuring taglibs classloading!");
                }

            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            System.exit(1);
        }
    }

    public Container(String[] args) {

        // Leave this here and first
        forceTaglibsLoading();

        try {

            initOptions();
            parseCommandLineOptions(args);
            configureLogging();

        } catch (Throwable t) {
            // Here we print the error to standard error as the logging setup
            // could be incomplete
            System.err.println("Error starting voms-admin server: " + t.getMessage());
            t.printStackTrace(System.err);
            System.exit(1);
        }

        try {

            loadConfiguration();
            logStartupConfiguration();
            configureJettyServer();
            start();
        } catch (Throwable t) {
            log.error("Error starting voms-admin server: " + t.getMessage(), t);
            System.exit(1);
        }
    }

    private void logStartupConfiguration() {

        log.info("VOMS Admin version {}.", Version.version());
        log.info("Hostname: {}", host);

        if (bindAddress != null) {
            log.info("Binding on: {}:{}", bindAddress, port);
        } else {
            log.info("Binding on all interfaces on port: {}", port);
        }

        if (tlsIncludeProtocols != null) {
            log.info("TLS included protocols: {}", tlsIncludeProtocols);
        }

        if (tlsExcludeProtocols != null) {
            log.info("TLS excluded protocols: {}", tlsExcludeProtocols);
        }

        if (tlsIncludeCipherSuites != null) {
            log.info("TLS included cipher suites: {}", tlsIncludeCipherSuites);
        }

        if (tlsExcludeCipherSuites != null) {
            log.info("TLS excluded cipher suites: {}", tlsExcludeCipherSuites);
        }

        if (!tlsRequireClientCert) {
            log.warn("TLS require client certificate: {}.", tlsRequireClientCert);
        } else {
            log.info("TLS require client certificate: {}", tlsRequireClientCert);
        }

        log.info("HTTP status handler listening on: {}", statusPort);
        log.info("Service credentials: {}, {}", certFile, keyFile);
        log.info("Trust anchors directory: {}", trustDir);
        log.info("Trust anchors directory refresh interval (in minutes): {}",
                TimeUnit.MILLISECONDS.toMinutes(trustDirRefreshIntervalInMsec));
        log.info("Web archive location: {}", war);
        log.info("Configuration dir: {}", confDir);
        log.info("Deployment dir: {}", deployDir);

        log.info("Max # of concurrent connections: {}",
                getConfigurationProperty(ConfigurationProperty.MAX_CONNECTIONS));

        log.info("Max request queue size: {}",
                getConfigurationProperty(ConfigurationProperty.MAX_REQUEST_QUEUE_SIZE));

    }

    private void configureLogging() {

        String loggingConf = String.format("%s/%s", confDir, "voms-admin-server.logback").replaceAll("/+", "/");

        File f = new File(loggingConf);

        if (!f.exists() || !f.canRead()) {
            log.error("Error loading logging configuration: " + "{} does not exist or is not readable.");
            return;
        }

        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
        lc.setName(LOGGER_CONTEXT_NAME);
        JoranConfigurator configurator = new JoranConfigurator();

        configurator.setContext(lc);
        lc.reset();

        try {
            configurator.doConfigure(loggingConf);

        } catch (JoranException e) {

            failAndExit("Error setting up the logging system", e);

        }
    }

    public static void main(String[] args) {

        new Container(args);
    }

}