/* * Copyright 2016 KairosDB Authors * * 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.kairosdb.core.http; import com.google.inject.Inject; import com.google.inject.name.Named; import com.google.inject.servlet.GuiceFilter; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.security.SecurityHandler; import org.eclipse.jetty.security.authentication.BasicAuthenticator; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.security.Credential; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.ExecutorThreadPool; import org.kairosdb.core.KairosDBService; import org.kairosdb.core.exception.KairosDBException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkNotNull; import static org.kairosdb.util.Preconditions.checkNotNullOrEmpty; public class WebServer implements KairosDBService { public static final Logger logger = LoggerFactory.getLogger(WebServer.class); public static final String JETTY_ADDRESS_PROPERTY = "kairosdb.jetty.address"; public static final String JETTY_PORT_PROPERTY = "kairosdb.jetty.port"; public static final String JETTY_WEB_ROOT_PROPERTY = "kairosdb.jetty.static_web_root"; public static final String JETTY_AUTH_USER_PROPERTY = "kairosdb.jetty.basic_auth.user"; public static final String JETTY_AUTH_PASSWORD_PROPERTY = "kairosdb.jetty.basic_auth.password"; public static final String JETTY_SSL_PORT = "kairosdb.jetty.ssl.port"; public static final String JETTY_SSL_PROTOCOLS = "kairosdb.jetty.ssl.protocols"; public static final String JETTY_SSL_CIPHER_SUITES = "kairosdb.jetty.ssl.cipherSuites"; public static final String JETTY_SSL_KEYSTORE_PATH = "kairosdb.jetty.ssl.keystore.path"; public static final String JETTY_SSL_KEYSTORE_PASSWORD = "kairosdb.jetty.ssl.keystore.password"; public static final String JETTY_THREADS_QUEUE_SIZE_PROPERTY = "kairosdb.jetty.threads.queue_size"; public static final String JETTY_THREADS_MIN_PROPERTY = "kairosdb.jetty.threads.min"; public static final String JETTY_THREADS_MAX_PROPERTY = "kairosdb.jetty.threads.max"; public static final String JETTY_THREADS_KEEP_ALIVE_MS_PROPERTY = "kairosdb.jetty.threads.keep_alive_ms"; private InetAddress m_address; private int m_port; private String m_webRoot; private Server m_server; private String m_authUser = null; private String m_authPassword = null; private int m_sslPort; private String[] m_cipherSuites; private String[] m_protocols; private String m_keyStorePath; private String m_keyStorePassword; private ExecutorThreadPool m_pool; public WebServer(int port, String webRoot) throws UnknownHostException { this(null, port, webRoot); } @Inject public WebServer(@Named(JETTY_ADDRESS_PROPERTY) String address, @Named(JETTY_PORT_PROPERTY) int port, @Named(JETTY_WEB_ROOT_PROPERTY) String webRoot) throws UnknownHostException { checkNotNull(webRoot); m_port = port; m_webRoot = webRoot; m_address = InetAddress.getByName(address); } @Inject(optional = true) public void setAuthCredentials(@Named(JETTY_AUTH_USER_PROPERTY) String user, @Named(JETTY_AUTH_PASSWORD_PROPERTY) String password) { m_authUser = user; m_authPassword = password; } @Inject(optional = true) public void setSSLSettings(@Named(JETTY_SSL_PORT) int sslPort, @Named(JETTY_SSL_KEYSTORE_PATH) String keyStorePath, @Named(JETTY_SSL_KEYSTORE_PASSWORD) String keyStorePassword) { m_sslPort = sslPort; m_keyStorePath = checkNotNullOrEmpty(keyStorePath); m_keyStorePassword = checkNotNullOrEmpty(keyStorePassword); } @Inject(optional = true) public void setSSLCipherSuites(@Named(JETTY_SSL_CIPHER_SUITES) String cipherSuites) { checkNotNull(cipherSuites); m_cipherSuites = cipherSuites.split("\\s*,\\s*"); } @Inject(optional = true) public void setSSLProtocols(@Named(JETTY_SSL_PROTOCOLS) String protocols) { m_protocols = protocols.split("\\s*,\\s*"); } @Inject(optional = true) public void setThreadPool(@Named(JETTY_THREADS_QUEUE_SIZE_PROPERTY) int maxQueueSize, @Named(JETTY_THREADS_MIN_PROPERTY) int minThreads, @Named(JETTY_THREADS_MAX_PROPERTY) int maxThreads, @Named(JETTY_THREADS_KEEP_ALIVE_MS_PROPERTY) long keepAliveMs) { LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(maxQueueSize); m_pool = new ExecutorThreadPool(minThreads, maxThreads, keepAliveMs, TimeUnit.MILLISECONDS, queue); } @Override public void start() throws KairosDBException { try { if (m_port > 0) m_server = new Server(new InetSocketAddress(m_address, m_port)); else m_server = new Server(); if (m_pool != null) m_server.setThreadPool(m_pool); //Set up SSL if (m_keyStorePath != null && !m_keyStorePath.isEmpty()) { logger.info("Using SSL"); SslContextFactory sslContextFactory = new SslContextFactory(m_keyStorePath); if (m_cipherSuites != null && m_cipherSuites.length > 0) sslContextFactory.setIncludeCipherSuites(m_cipherSuites); if (m_protocols != null && m_protocols.length > 0) sslContextFactory.setIncludeProtocols(m_protocols); sslContextFactory.setKeyStorePassword(m_keyStorePassword); SslSelectChannelConnector selectChannelConnector = new SslSelectChannelConnector(sslContextFactory); selectChannelConnector.setPort(m_sslPort); m_server.addConnector(selectChannelConnector); } ServletContextHandler servletContextHandler = new ServletContextHandler(); //Turn on basic auth if the user was specified if (m_authUser != null) { servletContextHandler.setSecurityHandler(basicAuth(m_authUser, m_authPassword, "kairos")); servletContextHandler.setContextPath("/"); } servletContextHandler.addFilter(GuiceFilter.class, "/api/*", null); servletContextHandler.addServlet(DefaultServlet.class, "/api/*"); ResourceHandler resourceHandler = new ResourceHandler(); resourceHandler.setDirectoriesListed(true); resourceHandler.setWelcomeFiles(new String[]{"index.html"}); resourceHandler.setResourceBase(m_webRoot); resourceHandler.setAliases(true); HandlerList handlers = new HandlerList(); handlers.setHandlers(new Handler[]{servletContextHandler, resourceHandler, new DefaultHandler()}); m_server.setHandler(handlers); m_server.start(); } catch (Exception e) { throw new KairosDBException(e); } } @Override public void stop() { try { if (m_server != null) { m_server.stop(); m_server.join(); } } catch (Exception e) { logger.error("Error stopping web server", e); } } public InetAddress getAddress() { return m_address; } private static SecurityHandler basicAuth(String username, String password, String realm) { HashLoginService l = new HashLoginService(); l.putUser(username, Credential.getCredential(password), new String[]{"user"}); l.setName(realm); Constraint constraint = new Constraint(); constraint.setName(Constraint.__BASIC_AUTH); constraint.setRoles(new String[]{"user"}); constraint.setAuthenticate(true); Constraint noConstraint = new Constraint(); ConstraintMapping healthcheckConstraintMapping = new ConstraintMapping(); healthcheckConstraintMapping.setConstraint(noConstraint); healthcheckConstraintMapping.setPathSpec("/api/v1/health/check"); ConstraintMapping cm = new ConstraintMapping(); cm.setConstraint(constraint); cm.setPathSpec("/*"); ConstraintSecurityHandler csh = new ConstraintSecurityHandler(); csh.setAuthenticator(new BasicAuthenticator()); csh.setRealmName("myrealm"); csh.addConstraintMapping(healthcheckConstraintMapping); csh.addConstraintMapping(cm); csh.setLoginService(l); return csh; } }