/*
* Copyright (c) 2012-2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.security;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.server.session.AbstractSessionManager;
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.filter.CompositeFilter;
import org.springframework.web.filter.DelegatingFilterProxy;
import com.emc.storageos.coordinator.client.service.CoordinatorClient;
import com.emc.storageos.coordinator.common.Service;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.security.authentication.SecurityDisablerFilter;
import com.emc.storageos.security.keystore.impl.KeyStoreUtil;
import com.emc.storageos.security.keystore.impl.KeystoreEngine;
import com.sun.jersey.api.core.DefaultResourceConfig;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.spi.container.ContainerResponseFilter;
import com.sun.jersey.spi.container.ResourceFilterFactory;
import com.sun.jersey.spi.container.servlet.ServletContainer;
/**
* Base class for services including SSL connectors setup.
* Some common filter and handler work is there in initServer().
* Derived class can override initServer() to alter the way filters, handlers
* and other specifics are getting handled.
*/
public abstract class AbstractSecuredWebServer {
private final Logger _log = LoggerFactory.getLogger(getClass());
private String _unsecurePort;
private String _securePort;
private String _bindAddress;
private String _httpBindAddress;
protected SelectChannelConnector _unsecuredConnector = null;
protected SslSelectChannelConnector _securedConnector = null;
protected Server _server;
protected Application _app;
protected DbClient _dbClient;
private ResourceFilterFactory _resourceFilterFactory;
private Boolean _disableSSL = false;
private Boolean _disableHTTP = false;
protected CompositeFilter _secFilters;
private SecurityDisablerFilter _disablingFilter;
private ContainerResponseFilter _responseFilter;
private Service _serviceInfo;
private String[] _ciphers;
protected ServletContextHandler servletHandler;
private CoordinatorClient _coordinatorClient;
private ThreadPool threadPool;
private Integer lowResourcesConnections;
private Integer lowResourcesMaxIdleTime;
private Integer minQueueThreads;
private Integer maxQueueThreads;
private Integer maxQueued;
private boolean startDbClientInBackground;
public void setUnsecuredConnector(SelectChannelConnector unsecuredConnector) {
_unsecuredConnector = unsecuredConnector;
}
// Not a real issue as no write in class
public void setCiphersToInclude(String[] ciphers) { // NOSONAR ("Suppressing: The user-supplied array 'ciphers' is stored directly.")
_ciphers = ciphers;
}
public void setServiceInfo(Service service) {
_serviceInfo = service;
}
@Autowired(required = false)
private SecurityDisabler _disabler;
public void setSecFilters(CompositeFilter filters) {
_secFilters = filters;
}
public void setSecurityDisablingFilter(SecurityDisablerFilter filter) {
_disablingFilter = filter;
}
public void setContainerResponseFilter(ContainerResponseFilter filter) {
_responseFilter = filter;
}
public void setBindAddress(String bindAddress) {
_bindAddress = bindAddress;
}
public void setHttpBindAddress(String bindAddress) {
_httpBindAddress = bindAddress;
}
public void setSecurePort(String securePort) {
_securePort = securePort;
}
public void setUnsecurePort(String unsecurePort) {
_unsecurePort = unsecurePort;
}
public void setApplication(Application app) {
_app = app;
}
public void setDbClient(DbClient dbClient) {
_dbClient = dbClient;
}
public void setResourceFilterFactory(ResourceFilterFactory filterFactory) {
_resourceFilterFactory = filterFactory;
}
public void setDisableSSL(Boolean disable) {
_disableSSL = disable;
}
public void setDisableHTTP(Boolean disable) {
_disableHTTP = disable;
}
public void setCoordinator(CoordinatorClient coord) {
_coordinatorClient = coord;
}
public void setLowResourcesConnections(Integer lowResourcesConnections) {
this.lowResourcesConnections = lowResourcesConnections;
}
public void setLowResourcesMaxIdleTime(Integer lowResourcesMaxIdleTime) {
this.lowResourcesMaxIdleTime = lowResourcesMaxIdleTime;
}
public void setMinQueueThreads(Integer minQueueThreads) {
this.minQueueThreads = minQueueThreads;
}
public void setMaxQueueThreads(Integer maxQueueThreads) {
this.maxQueueThreads = maxQueueThreads;
}
public void setMaxQueued(Integer maxQueued) {
this.maxQueued = maxQueued;
}
public void setStartDbClientInBackground(boolean startDbClientInBackground) {
this.startDbClientInBackground = startDbClientInBackground;
}
public Server getServer() {
return _server;
}
/**
* set up the ssl connectors with strong ciphers
*
* @throws Exception
*/
protected void initConnectors() throws Exception {
if (!_disableHTTP) {
if (_unsecuredConnector == null) {
_unsecuredConnector = new SelectChannelConnector();
}
if (_unsecurePort != null) {
_unsecuredConnector.setPort(Integer.parseInt(_unsecurePort));
} else {
_unsecuredConnector.setPort(_serviceInfo.getEndpoint().getPort());
}
if (_httpBindAddress != null) {
_unsecuredConnector.setHost(_httpBindAddress);
}
if (lowResourcesConnections != null) {
_unsecuredConnector.setLowResourcesConnections(lowResourcesConnections);
}
if (lowResourcesMaxIdleTime != null) {
_unsecuredConnector.setLowResourcesMaxIdleTime(lowResourcesMaxIdleTime);
}
if (threadPool != null) {
_unsecuredConnector.setThreadPool(threadPool);
}
_server.addConnector(_unsecuredConnector);
}
if (!_disableSSL) {
SslContextFactory sslFac = new SslContextFactory();
sslFac.setIncludeCipherSuites(_ciphers);
KeyStore ks = KeyStoreUtil.getViPRKeystore(_coordinatorClient);
_log.debug("The certificates in Jetty is {}. ", ks.getCertificateChain(KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS));
sslFac.setCertAlias(KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS);
sslFac.setKeyStore(ks);
_securedConnector = new SslSelectChannelConnector(sslFac);
if (_securePort != null) {
_securedConnector.setPort(Integer.parseInt(_securePort));
} else {
_securedConnector.setPort(_serviceInfo.getEndpoint().getPort());
}
if (_bindAddress != null) {
_securedConnector.setHost(_bindAddress);
}
if (lowResourcesConnections != null) {
_securedConnector.setLowResourcesConnections(lowResourcesConnections);
}
if (lowResourcesMaxIdleTime != null) {
_securedConnector.setLowResourcesMaxIdleTime(lowResourcesMaxIdleTime);
}
if (threadPool != null) {
_securedConnector.setThreadPool(threadPool);
}
_server.addConnector(_securedConnector);
}
_server.setSendServerVersion(false);
}
protected void initThreadPool() {
if (minQueueThreads == null && maxQueueThreads == null && maxQueued == null) {
return;
}
QueuedThreadPool tp = new QueuedThreadPool();
if (minQueueThreads != null) {
tp.setMinThreads(minQueueThreads);
}
if (maxQueueThreads != null) {
tp.setMaxThreads(maxQueueThreads);
}
if (maxQueued != null) {
tp.setMaxQueued(maxQueued);
}
threadPool = tp;
}
/**
* Initialize server handlers, rest resources.
*
* @throws Exception
*/
protected void initServer() throws Exception {
_server = new Server();
initThreadPool();
initConnectors();
// AuthN servlet filters
servletHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
servletHandler.setContextPath("/");
_server.setHandler(servletHandler);
((AbstractSessionManager) servletHandler.getSessionHandler().getSessionManager()).setUsingCookies(false);
if (_disabler != null) {
final FilterHolder securityFilterHolder = new FilterHolder(
new DelegatingFilterProxy(_disablingFilter));
servletHandler.addFilter(securityFilterHolder, "/*", FilterMapping.REQUEST);
_log.warn("security checks are disabled... skipped adding security filters");
} else {
final FilterHolder securityFilterHolder = new FilterHolder(new DelegatingFilterProxy(_secFilters));
servletHandler.addFilter(securityFilterHolder, "/*", FilterMapping.REQUEST);
}
// Add the REST resources
if (_app != null) {
ResourceConfig config = new DefaultResourceConfig();
config.add(_app);
Map<String, MediaType> type = config.getMediaTypeMappings();
type.put("json", MediaType.APPLICATION_JSON_TYPE);
type.put("xml", MediaType.APPLICATION_XML_TYPE);
type.put("octet-stream", MediaType.APPLICATION_OCTET_STREAM_TYPE);
type.put("form-data", MediaType.MULTIPART_FORM_DATA_TYPE);
servletHandler.addServlet(new ServletHolder(new ServletContainer(config)), "/*");
// AuthZ resource filters
Map<String, Object> props = new HashMap<String, Object>();
props.put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, _resourceFilterFactory);
// Adding the ContainerResponseFilter
props.put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, _responseFilter);
config.setPropertiesAndFeatures(props);
}
if (_dbClient != null) {
// in some cases, like syssvc, we don't want the service to be blocked by dbsvc startup.
// Otherwise there could be a dependency loop between services.
if (startDbClientInBackground) {
_log.info("starting dbclient in background");
new Thread() {
public void run() {
_dbClient.start();
}
}.start();
} else {
_log.info("starting dbclient");
_dbClient.start();
}
}
}
}