http://git-wip-us.apache.org/repos/asf/sentry/blob/ea7a33b7/sentry-service/sentry-service-server/src/main/webapp/css/sentry.css ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-server/src/main/webapp/css/sentry.css b/sentry-service/sentry-service-server/src/main/webapp/css/sentry.css deleted file mode 100644 index 69cba19..0000000 --- a/sentry-service/sentry-service-server/src/main/webapp/css/sentry.css +++ /dev/null @@ -1,52 +0,0 @@ -/** - * 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. - */ - -html { - position: relative; - min-height: 100%; -} - -body { - /* Margin bottom by footer height */ - margin-bottom: 60px; - padding-top: 80px; -} - -.navbar-collapse {margin-top:10px} - -.footer { - position: absolute; - bottom: 0; - width: 100%; - /* Set the fixed height of the footer here */ - height: 60px; - background-color: #f5f5f5; -} - -.container .text-muted { - margin: 20px 0; -} - -.footer > .container { - padding-right: 15px; - padding-left: 15px; -} - -code { - font-size: 80%; -}
http://git-wip-us.apache.org/repos/asf/sentry/blob/ea7a33b7/sentry-service/sentry-service-server/src/main/webapp/sentry.png ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-server/src/main/webapp/sentry.png b/sentry-service/sentry-service-server/src/main/webapp/sentry.png deleted file mode 100644 index 67edd90..0000000 Binary files a/sentry-service/sentry-service-server/src/main/webapp/sentry.png and /dev/null differ http://git-wip-us.apache.org/repos/asf/sentry/blob/ea7a33b7/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/api/service/thrift/TestSentryWebServerWithoutSecurity.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/api/service/thrift/TestSentryWebServerWithoutSecurity.java b/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/api/service/thrift/TestSentryWebServerWithoutSecurity.java index 6e741e8..29adced 100644 --- a/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/api/service/thrift/TestSentryWebServerWithoutSecurity.java +++ b/sentry-service/sentry-service-server/src/test/java/org/apache/sentry/api/service/thrift/TestSentryWebServerWithoutSecurity.java @@ -22,6 +22,7 @@ import java.net.URL; import org.apache.commons.io.IOUtils; import org.apache.sentry.service.thrift.SentryServiceIntegrationBase; +import org.apache.sentry.service.web.ConfServlet; import org.junit.After; import org.junit.Assert; import org.junit.Before; http://git-wip-us.apache.org/repos/asf/sentry/blob/ea7a33b7/sentry-service/sentry-service-web/pom.xml ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-web/pom.xml b/sentry-service/sentry-service-web/pom.xml new file mode 100644 index 0000000..53c6e3f --- /dev/null +++ b/sentry-service/sentry-service-web/pom.xml @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + ~ + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>sentry-service</artifactId> + <groupId>org.apache.sentry</groupId> + <version>2.2.0-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.apache.sentry</groupId> + <artifactId>sentry-service-web</artifactId> + + <build> + <sourceDirectory>${basedir}/src/main/java</sourceDirectory> + <testSourceDirectory>${basedir}/src/test/java</testSourceDirectory> + <resources> + <resource> + <directory>${basedir}/src/main</directory> + <includes> + <include>webapp/**</include> + </includes> + <filtering>true</filtering> + </resource> + <resource> + <directory>${basedir}/src/main/resources</directory> + </resource> + </resources> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <configuration> + <nonFilteredFileExtensions> + <nonFilteredFileExtension>eot</nonFilteredFileExtension> + <nonFilteredFileExtension>png</nonFilteredFileExtension> + <nonFilteredFileExtension>svg</nonFilteredFileExtension> + <nonFilteredFileExtension>ttf</nonFilteredFileExtension> + <nonFilteredFileExtension>woff</nonFilteredFileExtension> + <nonFilteredFileExtension>woff2</nonFilteredFileExtension> + </nonFilteredFileExtensions> + </configuration> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sentry</groupId> + <artifactId>sentry-core-common</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.sentry</groupId> + <artifactId>sentry-service-providers</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.sentry</groupId> + <artifactId>sentry-spi</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-server</artifactId> + <version>${jetty.version}</version> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-servlet</artifactId> + <version>${jetty.version}</version> + </dependency> + <dependency> + <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-common</artifactId> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-all</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-minikdc</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> +</project> http://git-wip-us.apache.org/repos/asf/sentry/blob/ea7a33b7/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/ConfServlet.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/ConfServlet.java b/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/ConfServlet.java new file mode 100644 index 0000000..b0fda0e --- /dev/null +++ b/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/ConfServlet.java @@ -0,0 +1,72 @@ +/* + * 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.sentry.service.web; + +import java.io.IOException; +import java.io.Writer; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.hadoop.conf.Configuration; + +import static org.apache.commons.lang.StringEscapeUtils.escapeHtml; + +/** + * Servlet to print out all sentry configuration. + */ +public class ConfServlet extends HttpServlet { + public static final String CONF_CONTEXT_ATTRIBUTE = "sentry.conf"; + public static final String FORMAT_JSON = "json"; + public static final String FORMAT_XML = "xml"; + public static final String FORMAT_PARAM = "format"; + private static final long serialVersionUID = 1L; + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String format = request.getParameter(FORMAT_PARAM); + if (format == null) { + format = FORMAT_XML; + } + + if (FORMAT_XML.equals(format)) { + response.setContentType("text/xml; charset=utf-8"); + } else if (FORMAT_JSON.equals(format)) { + response.setContentType("application/json; charset=utf-8"); + } + + Configuration conf = (Configuration)getServletContext().getAttribute( + CONF_CONTEXT_ATTRIBUTE); + assert conf != null; + + Writer out = response.getWriter(); + if (FORMAT_JSON.equals(format)) { + Configuration.dumpConfiguration(conf, out); + } else if (FORMAT_XML.equals(format)) { + conf.writeXml(out); + } else { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Bad format: " + escapeHtml(format)); + } + out.close(); + } +} http://git-wip-us.apache.org/repos/asf/sentry/blob/ea7a33b7/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/DefaultWebServicesProvider.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/DefaultWebServicesProvider.java b/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/DefaultWebServicesProvider.java new file mode 100644 index 0000000..9aafa29 --- /dev/null +++ b/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/DefaultWebServicesProvider.java @@ -0,0 +1,168 @@ +/* + * 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.sentry.service.web; + +import com.google.common.base.Preconditions; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.servlet.DispatcherType; +import lombok.extern.slf4j.Slf4j; +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.server.provider.webservice.AttributeDesc; +import org.apache.sentry.server.provider.webservice.FilterDesc; +import org.apache.sentry.server.provider.webservice.WebServiceProvider; +import org.apache.sentry.server.provider.webservice.WebServiceProviderFactory; +import org.apache.sentry.server.provider.webservice.ServletDesc; +import org.apache.sentry.service.common.ServiceConstants.ServerConfig; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletHolder; + +/** + * Provides the Web Interface functions for default Sentry services. + */ +@Slf4j +public class DefaultWebServicesProvider implements WebServiceProvider, + WebServiceProviderFactory { + + public static final String ID = "default"; + private static final String STATIC_RESOURCE_DIR = "/webapp"; + + private Configuration config; + + @Override + public List<ServletDesc> getServlets() { + List<ServletDesc> servlets = new ArrayList<>(); + servlets.add(ServletDesc.of("/conf", new ServletHolder(ConfServlet.class))); + servlets.add(ServletDesc.of("/admin/logLevel", new ServletHolder(LogLevelServlet.class))); + if (config.getBoolean(ServerConfig.SENTRY_WEB_PUBSUB_SERVLET_ENABLED, + ServerConfig.SENTRY_WEB_PUBSUB_SERVLET_ENABLED_DEFAULT)) { + servlets.add(ServletDesc.of("/admin/publishMessage", new ServletHolder(PubSubServlet.class))); + } + if (config.getBoolean(ServerConfig.SENTRY_WEB_ADMIN_SERVLET_ENABLED, + ServerConfig.SENTRY_WEB_ADMIN_SERVLET_ENABLED_DEFAULT)) { + // Static files holder + ServletHolder staticHolder = new ServletHolder(new DefaultServlet()); + staticHolder.setInitParameter("pathInfoOnly", "true"); + URL url = this.getClass().getResource(STATIC_RESOURCE_DIR); + staticHolder.setInitParameter("resourceBase", url.toString()); + servlets.add(ServletDesc.of("/*", staticHolder)); + } + + return servlets; + } + + @Override + public List<AttributeDesc> getAttributes() { + return Arrays.asList(AttributeDesc.of(ConfServlet.CONF_CONTEXT_ATTRIBUTE, config)); + } + + @Override + public List<FilterDesc> getFilters() { + List<FilterDesc> filters = new ArrayList<>(); + String authMethod = config.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 sentryAuthFilterHolder = new FilterHolder(SentryAuthFilter.class); + sentryAuthFilterHolder.setInitParameters(loadWebAuthenticationConf(config)); + filters.add(FilterDesc.of("/*", sentryAuthFilterHolder, EnumSet.of(DispatcherType.REQUEST))); + } + return filters; + } + + 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); + } + } + + @Override + public void init(Configuration config) { + this.config = config; + } + + @Override + public WebServiceProvider create() { + return this; + } + + @Override + public String getId() { + return ID; + } + + @Override + public void close() { + + } +} http://git-wip-us.apache.org/repos/asf/sentry/blob/ea7a33b7/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/LogLevelServlet.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/LogLevelServlet.java b/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/LogLevelServlet.java new file mode 100644 index 0000000..a496779 --- /dev/null +++ b/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/LogLevelServlet.java @@ -0,0 +1,123 @@ +/* + * 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.sentry.service.web; + +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; + +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +import static org.apache.commons.lang.StringEscapeUtils.escapeHtml; + +public class LogLevelServlet extends HttpServlet { + private static final String LF = "\n"; + private static final String BR = "<br />"; + private static final String B_BR = "<b>%s</b><br />"; + private static final String FORMS_HEAD = + "<h1>" + "Log Level" + "</h1>" + + LF + BR + "<hr /><h3>Results</h3>" + + LF + " Submitted Log Name: " + B_BR; + private static final String FORMS_CONTENT_GET = + LF + " Effective level: " + B_BR; + private static final String FORMS_CONTENT_SET = + LF + " Submitted Level: " + B_BR + + LF + " Setting Level to %s" + BR + + LF + " Effective level: " + B_BR; + private static final String FORMS_END = + LF + BR + "<hr /><h3>Get / Set</h3>" + + LF + "<form>Log: <input type='text' size='50' name='log' /> " + + "<input type='submit' value='Get Log Level' />" + "</form>" + + LF + "<form>Log: <input type='text' size='50' name='log' /> " + + "Level: <input type='text' name='level' /> " + + "<input type='submit' value='Set Log Level' />" + "</form>"; + private static final String FORMS_GET = FORMS_HEAD + FORMS_CONTENT_GET; + private static final String FORMS_SET = FORMS_HEAD + FORMS_CONTENT_SET; + + /** + * Return parameter on servlet request for the given name + * + * @param request: Servlet request + * @param name: Name of parameter in servlet request + * @return Parameter in servlet request for the given name, return null if can't find parameter. + */ + private String getParameter(ServletRequest request, String name) { + String s = request.getParameter(name); + if (s == null) { + return null; + } + s = s.trim(); + return s.length() == 0 ? null : s; + } + + /** + * Check the validity of the log level. + * @param level: The log level to be checked + * @return + * true: The log level is valid + * false: The log level is invalid + */ + private boolean isLogLevelValid(String level) { + return level.equals(Level.toLevel(level).toString()); + } + + /** + * Parse the class name and log level in the http servlet request. + * If the request contains only class name, return the log level in the response message. + * If the request contains both class name and level, set the log level to the requested level + * and return the setting result in the response message. + */ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String logName = getParameter(request, "log"); + String level = getParameter(request, "level"); + response.setContentType("text/html;charset=utf-8"); + response.setStatus(HttpServletResponse.SC_OK); + PrintWriter out = response.getWriter(); + + if (logName != null) { + Logger logInstance = LogManager.getLogger(logName); + if (level == null) { + out.write(String.format(FORMS_GET, + escapeHtml(logName), + logInstance.getEffectiveLevel().toString())); + } else if (isLogLevelValid(level)) { + logInstance.setLevel(Level.toLevel(level)); + out.write(String.format(FORMS_SET, + escapeHtml(logName), + escapeHtml(level), + escapeHtml(level), + logInstance.getEffectiveLevel().toString())); + } else { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid log level: " + escapeHtml(level)); + return; + } + } + out.write(FORMS_END); + out.close(); + response.flushBuffer(); + } +} http://git-wip-us.apache.org/repos/asf/sentry/blob/ea7a33b7/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/PubSubServlet.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/PubSubServlet.java b/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/PubSubServlet.java new file mode 100644 index 0000000..c7e281b --- /dev/null +++ b/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/PubSubServlet.java @@ -0,0 +1,129 @@ +/* + * 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.sentry.service.web; + +import org.apache.sentry.core.common.utils.PubSub; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +import static org.apache.commons.lang.StringEscapeUtils.escapeHtml; + +/** + * This servlet facilitates sending {topic, message } tuples to Servlet components + * subscribed to specific topics. + * <p> + * It uses publish-subscribe mechanism implemented by PubSub class. + * The form generated by this servlet consists of the following elements: + * <p> + * a) Topic: pull-down menu of existing topics, i.e. the topics registered with + * PubSub by calling PubSub.subscribe() API. This prevents entering invalid topic. + * <p> + * b) Message: text field for entering a message + * <p> + * c) Submit: button to submit (topic, message) tuple + * <p> + * d) Status: text area printing status of the request or help information. + */ +public class PubSubServlet extends HttpServlet { + + private static final Logger LOGGER = LoggerFactory.getLogger(PubSubServlet.class); + + private static final String FORM_GET = + "<!DOCTYPE html>" + + "<html>" + + "<body>" + + "<form>" + + "<br><br><b>Topic:</b><br><br>" + + "<select name='topic'/>%s</select>" + + "<br><br><b>Message:</b><br><br>" + + "<input type='text' size='50' name='message'/>" + + "<br><br>" + + "<input type='submit' value='Submit'/>" + + "</form>" + + "<br><br><b>Status:</b><br><br>" + + "<textarea rows='4' cols='50'>%s</textarea>" + + "</body>" + + "</html>"; + + /** + * Return parameter on servlet request for the given name + * + * @param request: Servlet request + * @param name: Name of parameter in servlet request + * @return Parameter in servlet request for the given name, return null if can't find parameter. + */ + private static String getParameter(ServletRequest request, String name) { + String s = request.getParameter(name); + if (s == null) { + return null; + } + s = s.trim(); + return s.isEmpty() ? null : s; + } + + /** + * Parse the topic and message values and submit them via PubSub.submit() API. + * Reject request for unknown topic, i.e. topic no one is subscribed to. + */ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String topic = getParameter(request, "topic"); + String message = getParameter(request, "message"); + response.setContentType("text/html;charset=utf-8"); + response.setStatus(HttpServletResponse.SC_OK); + PrintWriter out = response.getWriter(); + + String msg = "Topic is required, Message is optional.\nValid topics: " + PubSub.getInstance().getTopics(); + if (topic != null) { + LOGGER.info("Submitting topic " + topic + ", message " + message); + try { + PubSub.getInstance().publish(PubSub.Topic.fromString(topic), message); + msg = "Submitted topic " + topic + ", message " + message; + } catch (Exception e) { + msg = "Failed to submit topic " + topic + ", message " + message + " - " + e.getMessage(); + LOGGER.error(msg); + response.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); + return; + } + } + + StringBuilder topics = new StringBuilder(); + for (PubSub.Topic t : PubSub.getInstance().getTopics()) { + topics.append("<option>").append(t.getName()).append("</option>"); + } + + String output = String.format(FORM_GET, topics.toString(), escapeHtml(msg)); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("HTML Page: " + output); + } + out.write(output); + out.close(); + response.flushBuffer(); + } +} http://git-wip-us.apache.org/repos/asf/sentry/blob/ea7a33b7/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/SentryAuthFilter.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/SentryAuthFilter.java b/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/SentryAuthFilter.java new file mode 100644 index 0000000..a6d75ad --- /dev/null +++ b/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/SentryAuthFilter.java @@ -0,0 +1,90 @@ +/* + * 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.sentry.service.web; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.Properties; +import java.util.Set; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.hadoop.security.authentication.server.AuthenticationFilter; +import org.apache.hadoop.util.StringUtils; +import org.apache.sentry.service.common.ServiceConstants.ServerConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Sets; + +/** + * SentryAuthFilter is a subclass of AuthenticationFilter, + * add authorization: Only allowed users could connect the web server. + */ +public class SentryAuthFilter extends AuthenticationFilter { + + private static final Logger LOG = LoggerFactory.getLogger(SentryAuthFilter.class); + + public static final String ALLOW_WEB_CONNECT_USERS = ServerConfig.SENTRY_WEB_SECURITY_ALLOW_CONNECT_USERS; + + private Set<String> allowUsers; + + @Override + protected void doFilter(FilterChain filterChain, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + String userName = request.getRemoteUser(); + LOG.debug("Authenticating user: " + userName + " from request."); + if (!allowUsers.contains(userName)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, + "Unauthorized user status code: " + HttpServletResponse.SC_FORBIDDEN); + throw new ServletException(userName + " is unauthorized. status code: " + HttpServletResponse.SC_FORBIDDEN); + } + super.doFilter(filterChain, request, response); + } + + /** + * Override <code>getConfiguration<code> to get <code>ALLOW_WEB_CONNECT_USERS<code>. + */ + @Override + protected Properties getConfiguration(String configPrefix, FilterConfig filterConfig) throws ServletException { + Properties props = new Properties(); + Enumeration<?> names = filterConfig.getInitParameterNames(); + while (names.hasMoreElements()) { + String name = (String) names.nextElement(); + if (name.startsWith(configPrefix)) { + String value = filterConfig.getInitParameter(name); + if (ALLOW_WEB_CONNECT_USERS.equals(name)) { + allowUsers = parseConnectUsersFromConf(value); + } else { + props.put(name.substring(configPrefix.length()), value); + } + } + } + return props; + } + + private static Set<String> parseConnectUsersFromConf(String value) { + //Removed the logic to convert the allowed users to lower case, as user names need to be case sensitive + return Sets.newHashSet(StringUtils.getStrings(value)); + } +} http://git-wip-us.apache.org/repos/asf/sentry/blob/ea7a33b7/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/SentryWebServer.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/SentryWebServer.java b/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/SentryWebServer.java new file mode 100644 index 0000000..61af4e1 --- /dev/null +++ b/sentry-service/sentry-service-web/src/main/java/org/apache/sentry/service/web/SentryWebServer.java @@ -0,0 +1,180 @@ +/* + * 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.sentry.service.web; + +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.Sets; +import java.util.EventListener; +import java.util.List; +import java.util.Set; +import lombok.extern.slf4j.Slf4j; +import org.apache.hadoop.conf.Configuration; +import org.apache.sentry.server.provider.webservice.AttributeDesc; +import org.apache.sentry.server.provider.webservice.FilterDesc; +import org.apache.sentry.server.provider.webservice.WebServiceProvider; +import org.apache.sentry.server.provider.webservice.WebServiceProviderFactory; +import org.apache.sentry.server.provider.webservice.WebServiceSpi; +import org.apache.sentry.server.provider.webservice.ServletDesc; +import org.apache.sentry.service.common.ServiceConstants.ServerConfig; +import org.apache.sentry.spi.ProviderManager; +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.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.server.handler.DefaultHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +@Slf4j +public class SentryWebServer { + + private Server server; + + public SentryWebServer(Configuration conf) { + server = new Server(); + + // Create a channel connector for "http/https" requests + ServerConnector connector; + int port = conf.getInt(ServerConfig.SENTRY_WEB_PORT, ServerConfig.SENTRY_WEB_PORT_DEFAULT); + 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 contextHandler = new ServletContextHandler(); + + // Load all of the Web Service Provider + + // get the web service providers + List<WebServiceProviderFactory> serviceProviderFactories = ProviderManager.getInstance() + .load(WebServiceSpi.ID); + + // initialize the factories + for (WebServiceProviderFactory providerFactory : serviceProviderFactories) { + providerFactory.init(conf); + WebServiceProvider provider = providerFactory.create(); + + // register its listeners + for (EventListener listener : provider.getListeners()) { + contextHandler.addEventListener(listener); + } + + //register its attributes + for (AttributeDesc attributeEntry : provider.getAttributes()) { + contextHandler.getServletContext() + .setAttribute(attributeEntry.getName(), attributeEntry.getAttribute()); + } + + // register its servlets + for (ServletDesc servletEntry : provider.getServlets()) { + contextHandler + .addServlet(servletEntry.getServletHolder(), servletEntry.getPathSpec()); + } + + // register its filters + for (FilterDesc filterDesc : provider.getFilters()) { + contextHandler.addFilter(filterDesc.getFilterHolder(), filterDesc.getPathSpec(), + filterDesc.getDispatcherTypes()); + } + } + + ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection(); + contextHandlerCollection.setHandlers(new Handler[]{contextHandler, + new DefaultHandler()}); + + 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(); + } + + +} http://git-wip-us.apache.org/repos/asf/sentry/blob/ea7a33b7/sentry-service/sentry-service-web/src/main/resources/META-INF/services/org.apache.sentry.server.provider.webservice.WebServiceProviderFactory ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-web/src/main/resources/META-INF/services/org.apache.sentry.server.provider.webservice.WebServiceProviderFactory b/sentry-service/sentry-service-web/src/main/resources/META-INF/services/org.apache.sentry.server.provider.webservice.WebServiceProviderFactory new file mode 100644 index 0000000..8111e69 --- /dev/null +++ b/sentry-service/sentry-service-web/src/main/resources/META-INF/services/org.apache.sentry.server.provider.webservice.WebServiceProviderFactory @@ -0,0 +1,20 @@ +# +# 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. +# +# + +org.apache.sentry.service.web.DefaultWebServicesProvider \ No newline at end of file http://git-wip-us.apache.org/repos/asf/sentry/blob/ea7a33b7/sentry-service/sentry-service-web/src/main/webapp/index.html ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-web/src/main/webapp/index.html b/sentry-service/sentry-service-web/src/main/webapp/index.html new file mode 100644 index 0000000..08df9d1 --- /dev/null +++ b/sentry-service/sentry-service-web/src/main/webapp/index.html @@ -0,0 +1,84 @@ +<!-- + ~ 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. + ~ + --> +<!DOCTYPE HTML> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>Sentry Service</title> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content=""> + <link href="/static/bootstrap/css/bootstrap-3.3.7.min.css" rel="stylesheet"> + <link href="/static/bootstrap/css/bootstrap-theme-3.3.7.min.css" rel="stylesheet"> + <link href="/static/materialdesign/css/materialdesignicons.min.css" rel="stylesheet"> + <link href="/static/css/sentry.css" rel="stylesheet"> + </head> + + <body> + <nav class="navbar navbar-default navbar-fixed-top"> + <div class="container"> + <div class="navbar-header"> + <a class="navbar-brand" href="#"><img src="/static/images/sentry.png" alt="Sentry Logo" width="48" height="36"/></a> + </div> + <div class="collapse navbar-collapse"> + <ul class="nav navbar-nav"> + <li class="active"><a href="#">Home</a></li> + </ul> + </div> + </div> + </nav> + + <div class="container"> + <div class="page-header"><h2>Sentry Service</h2></div> + <div class="panel panel-default"> + <div class="panel-heading"> + <h3 class="panel-title">Runtime Information</h3> + </div> + <div class="panel-body"> + <div><a href="/conf"><span class="mdi mdi-settings"></span> Configuration</a></div> + <div><a href="/healthcheck"><span class="mdi mdi-heart-pulse"></span> Health Checks</a></div> + <div><a href="/metrics?pretty=true"><span class="mdi mdi-counter"></span> Metrics</a></div> + <div><a href="/threads"><span class="mdi mdi-source-fork"></span> Threads</a></div> + </div> + </div> + <div class="panel panel-default"> + <div class="panel-heading"> + <h3 class="panel-title">Runtime Modifications</h3> + </div> + <div class="panel-body"> + <div><a href="/admin/logLevel"><span class="mdi mdi-card-bulleted-settings"></span> Log Level</a></div> + <div><a href="/admin/publishMessage"><span class="mdi mdi-message"></span> Publish Message</a></div> + </div> + </div> + <div class="panel panel-default"> + <div class="panel-heading"> + <h3 class="panel-title">Service Information</h3> + </div> + <div class="panel-body"> + <div><a href="/admin/roles"><span class="mdi mdi-account-group"></span> Roles</a></div> + </div> + </div> + </div> + + <footer class="footer"> + <div class="container"> + <p class="text-muted">${project.version}</p> + </div> + </footer> + </body> +</html>
