| package org.apache.sentry.api.service.thrift; |
| |
| /** |
| * 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. |
| */ |
| |
| import com.codahale.metrics.servlets.AdminServlet; |
| import com.google.common.base.Preconditions; |
| |
| import java.io.IOException; |
| import java.net.URL; |
| import java.util.EnumSet; |
| import java.util.EventListener; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import com.google.common.base.Splitter; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.Sets; |
| import javax.servlet.DispatcherType; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.security.SecurityUtil; |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.apache.hadoop.security.authentication.server.AuthenticationFilter; |
| import org.apache.sentry.service.common.ServiceConstants.ServerConfig; |
| import org.eclipse.jetty.security.ConstraintMapping; |
| import org.eclipse.jetty.security.ConstraintSecurityHandler; |
| import org.eclipse.jetty.server.Connector; |
| import org.eclipse.jetty.server.Handler; |
| import org.eclipse.jetty.server.HttpConfiguration; |
| import org.eclipse.jetty.server.HttpConnectionFactory; |
| import org.eclipse.jetty.server.SecureRequestCustomizer; |
| import org.eclipse.jetty.server.ServerConnector; |
| import org.eclipse.jetty.server.SslConnectionFactory; |
| import org.eclipse.jetty.server.handler.ContextHandler; |
| import org.eclipse.jetty.server.handler.ContextHandlerCollection; |
| import org.eclipse.jetty.server.handler.ResourceHandler; |
| import org.eclipse.jetty.server.Server; |
| import org.eclipse.jetty.servlet.FilterHolder; |
| import org.eclipse.jetty.servlet.ServletContextHandler; |
| import org.eclipse.jetty.servlet.ServletHolder; |
| import org.eclipse.jetty.util.resource.Resource; |
| import org.eclipse.jetty.util.security.Constraint; |
| import org.eclipse.jetty.util.ssl.SslContextFactory; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| public class SentryWebServer { |
| |
| private static final Logger LOGGER = LoggerFactory.getLogger(SentryWebServer.class); |
| private static final String RESOURCE_DIR = "/webapp"; |
| private static final String WELCOME_PAGE = "SentryService.html"; |
| |
| private Server server; |
| |
| public SentryWebServer(List<EventListener> listeners, int port, Configuration conf) { |
| server = new Server(); |
| |
| // Create a channel connector for "http/https" requests |
| ServerConnector connector; |
| if (conf.getBoolean(ServerConfig.SENTRY_WEB_USE_SSL, false)) { |
| SslContextFactory sslContextFactory = new SslContextFactory(); |
| sslContextFactory.setKeyStorePath(conf.get(ServerConfig.SENTRY_WEB_SSL_KEYSTORE_PATH, "")); |
| sslContextFactory.setKeyStorePassword( |
| conf.get(ServerConfig.SENTRY_WEB_SSL_KEYSTORE_PASSWORD, "")); |
| // Exclude SSL blacklist protocols |
| sslContextFactory.setExcludeProtocols(ServerConfig.SENTRY_SSL_PROTOCOL_BLACKLIST_DEFAULT); |
| Set<String> moreExcludedSSLProtocols = |
| Sets.newHashSet(Splitter.on(",").trimResults().omitEmptyStrings() |
| .split(Strings.nullToEmpty(conf.get(ServerConfig.SENTRY_SSL_PROTOCOL_BLACKLIST)))); |
| sslContextFactory.addExcludeProtocols(moreExcludedSSLProtocols.toArray( |
| new String[moreExcludedSSLProtocols.size()])); |
| |
| HttpConfiguration httpConfiguration = new HttpConfiguration(); |
| httpConfiguration.setSecurePort(port); |
| httpConfiguration.setSecureScheme("https"); |
| httpConfiguration.addCustomizer(new SecureRequestCustomizer()); |
| |
| connector = new ServerConnector( |
| server, |
| new SslConnectionFactory(sslContextFactory, "http/1.1"), |
| new HttpConnectionFactory(httpConfiguration)); |
| |
| LOGGER.info("Now using SSL mode."); |
| } else { |
| connector = new ServerConnector(server, new HttpConnectionFactory()); |
| } |
| |
| connector.setPort(port); |
| server.setConnectors(new Connector[] { connector }); |
| |
| ServletContextHandler servletContextHandler = new ServletContextHandler(); |
| ServletHolder servletHolder = new ServletHolder(AdminServlet.class); |
| servletContextHandler.addServlet(servletHolder, "/*"); |
| |
| for(EventListener listener:listeners) { |
| servletContextHandler.addEventListener(listener); |
| } |
| |
| servletContextHandler.addServlet(new ServletHolder(ConfServlet.class), "/conf"); |
| |
| if (conf.getBoolean(ServerConfig.SENTRY_WEB_ADMIN_SERVLET_ENABLED, |
| ServerConfig.SENTRY_WEB_ADMIN_SERVLET_ENABLED_DEFAULT)) { |
| servletContextHandler.addServlet( |
| new ServletHolder(SentryAdminServlet.class), "/admin/*"); |
| } |
| servletContextHandler.getServletContext() |
| .setAttribute(ConfServlet.CONF_CONTEXT_ATTRIBUTE, conf); |
| |
| servletContextHandler.addServlet(new ServletHolder(LogLevelServlet.class), "/admin/logLevel"); |
| |
| if (conf.getBoolean(ServerConfig.SENTRY_WEB_PUBSUB_SERVLET_ENABLED, |
| ServerConfig.SENTRY_WEB_PUBSUB_SERVLET_ENABLED_DEFAULT)) { |
| servletContextHandler.addServlet(new ServletHolder(PubSubServlet.class), "/admin/publishMessage"); |
| } |
| |
| ResourceHandler resourceHandler = new ResourceHandler(); |
| resourceHandler.setDirectoriesListed(true); |
| URL url = this.getClass().getResource(RESOURCE_DIR); |
| try { |
| resourceHandler.setBaseResource(Resource.newResource(url.toString())); |
| } catch (IOException e) { |
| LOGGER.error("Got exception while setBaseResource for Sentry Service web UI", e); |
| } |
| resourceHandler.setWelcomeFiles(new String[]{WELCOME_PAGE}); |
| ContextHandler contextHandler= new ContextHandler(); |
| contextHandler.setHandler(resourceHandler); |
| |
| ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection(); |
| contextHandlerCollection.setHandlers(new Handler[]{contextHandler, servletContextHandler}); |
| |
| String authMethod = conf.get(ServerConfig.SENTRY_WEB_SECURITY_TYPE); |
| if (!ServerConfig.SENTRY_WEB_SECURITY_TYPE_NONE.equalsIgnoreCase(authMethod)) { |
| /** |
| * SentryAuthFilter is a subclass of AuthenticationFilter and |
| * AuthenticationFilter tagged as private and unstable interface: |
| * While there are not guarantees that this interface will not change, |
| * it is fairly stable and used by other projects (ie - Oozie) |
| */ |
| FilterHolder filterHolder = servletContextHandler.addFilter(SentryAuthFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); |
| filterHolder.setInitParameters(loadWebAuthenticationConf(conf)); |
| } |
| |
| server.setHandler(disableTraceMethod(contextHandlerCollection)); |
| } |
| |
| /** |
| * Disables the HTTP TRACE method request which leads to Cross-Site Tracking (XST) problems. |
| * |
| * To disable it, we need to wrap the Handler (which has the HTTP TRACE enabled) with |
| * a constraint that denies access to the HTTP TRACE method. |
| * |
| * @param handler The Handler which has the HTTP TRACE enabled. |
| * @return A new Handler wrapped with the HTTP TRACE constraint and the Handler passed as parameter. |
| */ |
| private Handler disableTraceMethod(Handler handler) { |
| Constraint disableTraceConstraint = new Constraint(); |
| disableTraceConstraint.setName("Disable TRACE"); |
| disableTraceConstraint.setAuthenticate(true); |
| |
| ConstraintMapping mapping = new ConstraintMapping(); |
| mapping.setConstraint(disableTraceConstraint); |
| mapping.setMethod("TRACE"); |
| mapping.setPathSpec("/"); |
| |
| ConstraintSecurityHandler constraintSecurityHandler = new ConstraintSecurityHandler(); |
| constraintSecurityHandler.addConstraintMapping(mapping); |
| constraintSecurityHandler.setHandler(handler); |
| |
| return constraintSecurityHandler; |
| } |
| |
| public void start() throws Exception{ |
| server.start(); |
| } |
| public void stop() throws Exception{ |
| server.stop(); |
| } |
| public boolean isAlive() { |
| return server != null && server.isStarted(); |
| } |
| private static Map<String, String> loadWebAuthenticationConf(Configuration conf) { |
| Map<String,String> prop = new HashMap<String, String>(); |
| prop.put(AuthenticationFilter.CONFIG_PREFIX, ServerConfig.SENTRY_WEB_SECURITY_PREFIX); |
| String allowUsers = conf.get(ServerConfig.SENTRY_WEB_SECURITY_ALLOW_CONNECT_USERS); |
| if (allowUsers == null || allowUsers.equals("")) { |
| allowUsers = conf.get(ServerConfig.ALLOW_CONNECT); |
| conf.set(ServerConfig.SENTRY_WEB_SECURITY_ALLOW_CONNECT_USERS, allowUsers); |
| } |
| validateConf(conf); |
| for (Map.Entry<String, String> entry : conf) { |
| String name = entry.getKey(); |
| if (name.startsWith(ServerConfig.SENTRY_WEB_SECURITY_PREFIX)) { |
| String value = conf.get(name); |
| prop.put(name, value); |
| } |
| } |
| return prop; |
| } |
| |
| private static void validateConf(Configuration conf) { |
| String authHandlerName = conf.get(ServerConfig.SENTRY_WEB_SECURITY_TYPE); |
| Preconditions.checkNotNull(authHandlerName, "Web authHandler should not be null."); |
| String allowUsers = conf.get(ServerConfig.SENTRY_WEB_SECURITY_ALLOW_CONNECT_USERS); |
| Preconditions.checkNotNull(allowUsers, "Allow connect user(s) should not be null."); |
| if (ServerConfig.SENTRY_WEB_SECURITY_TYPE_KERBEROS.equalsIgnoreCase(authHandlerName)) { |
| String principal = conf.get(ServerConfig.SENTRY_WEB_SECURITY_PRINCIPAL); |
| Preconditions.checkNotNull(principal, "Kerberos principal should not be null."); |
| Preconditions.checkArgument(principal.length() != 0, "Kerberos principal is not right."); |
| String keytabFile = conf.get(ServerConfig.SENTRY_WEB_SECURITY_KEYTAB); |
| Preconditions.checkNotNull(keytabFile, "Keytab File should not be null."); |
| Preconditions.checkArgument(keytabFile.length() != 0, "Keytab File is not right."); |
| try { |
| UserGroupInformation.setConfiguration(conf); |
| String hostPrincipal = SecurityUtil.getServerPrincipal(principal, ServerConfig.RPC_ADDRESS_DEFAULT); |
| UserGroupInformation.loginUserFromKeytab(hostPrincipal, keytabFile); |
| } catch (IOException ex) { |
| throw new IllegalArgumentException("Can't use Kerberos authentication, principal [" |
| + principal + "] keytab [" + keytabFile + "]", ex); |
| } |
| LOGGER.info("Using Kerberos authentication, principal [{}] keytab [{}]", principal, keytabFile); |
| } |
| } |
| } |