diff --git a/CHANGES.txt b/CHANGES.txt index da887fd27a..417b1b4cf7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -32,6 +32,9 @@ Trunk (unreleased changes) HADOOP-7214. Add Common functionality necessary to provide an equivalent of /usr/bin/groups for Hadoop. (Aaron T. Myers via todd) + HADOOP-6832. Add an authentication plugin using a configurable static user + for the web UI. (Owen O'Malley and Todd Lipcon via cdouglas) + IMPROVEMENTS HADOOP-7042. Updates to test-patch.sh to include failed test names and diff --git a/src/java/core-default.xml b/src/java/core-default.xml index fa887a642b..5edd21e6ce 100644 --- a/src/java/core-default.xml +++ b/src/java/core-default.xml @@ -45,7 +45,7 @@ hadoop.http.filter.initializers - + org.apache.hadoop.http.lib.StaticUserWebFilter A comma separated list of class names. Each class in the list must extend org.apache.hadoop.http.FilterInitializer. The corresponding Filter will be initialized. Then, the Filter will be applied to all user diff --git a/src/java/org/apache/hadoop/http/lib/StaticUserWebFilter.java b/src/java/org/apache/hadoop/http/lib/StaticUserWebFilter.java new file mode 100644 index 0000000000..f1ee20cfe9 --- /dev/null +++ b/src/java/org/apache/hadoop/http/lib/StaticUserWebFilter.java @@ -0,0 +1,150 @@ +/** + * 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.hadoop.http.lib; + +import java.io.IOException; +import java.security.Principal; +import java.util.HashMap; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.http.FilterContainer; +import org.apache.hadoop.http.FilterInitializer; + +import javax.servlet.Filter; + +/** + * Provides a servlet filter that pretends to authenticate a fake user (Dr.Who) + * so that the web UI is usable for a secure cluster without authentication. + */ +public class StaticUserWebFilter extends FilterInitializer { + static final String DEPRECATED_UGI_KEY = "dfs.web.ugi"; + + static final String USERNAME_KEY = "hadoop.http.staticuser.user"; + static final String USERNAME_DEFAULT = "dr.who"; + + private static final Log LOG = LogFactory.getLog(StaticUserWebFilter.class); + + static class User implements Principal { + private final String name; + public User(String name) { + this.name = name; + } + @Override + public String getName() { + return name; + } + @Override + public int hashCode() { + return name.hashCode(); + } + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other == null || other.getClass() != getClass()) { + return false; + } + return ((User) other).name.equals(name); + } + @Override + public String toString() { + return name; + } + } + + public static class StaticUserFilter implements Filter { + private User user; + private String username; + + @Override + public void destroy() { + // NOTHING + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain + ) throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + // if the user is already authenticated, don't override it + if (httpRequest.getRemoteUser() != null) { + chain.doFilter(request, response); + } else { + HttpServletRequestWrapper wrapper = + new HttpServletRequestWrapper(httpRequest) { + @Override + public Principal getUserPrincipal() { + return user; + } + @Override + public String getRemoteUser() { + return username; + } + }; + chain.doFilter(wrapper, response); + } + } + + @Override + public void init(FilterConfig conf) throws ServletException { + this.username = conf.getInitParameter(USERNAME_KEY); + this.user = new User(username); + } + + } + + @Override + public void initFilter(FilterContainer container, Configuration conf) { + HashMap options = new HashMap(); + + String username = getUsernameFromConf(conf); + options.put(USERNAME_KEY, username); + + container.addFilter("static_user_filter", + StaticUserFilter.class.getName(), + options); + } + + /** + * Retrieve the static username from the configuration. + */ + static String getUsernameFromConf(Configuration conf) { + String oldStyleUgi = conf.get(DEPRECATED_UGI_KEY); + if (oldStyleUgi != null) { + // We can't use the normal configuration deprecation mechanism here + // since we need to split out the username from the configured UGI. + LOG.warn(DEPRECATED_UGI_KEY + " should not be used. Instead, use " + + USERNAME_KEY + "."); + String[] parts = oldStyleUgi.split(","); + return parts[0]; + } else { + return conf.get(USERNAME_KEY, USERNAME_DEFAULT); + } + } + +} diff --git a/src/java/org/apache/hadoop/http/lib/package.html b/src/java/org/apache/hadoop/http/lib/package.html new file mode 100644 index 0000000000..c270746674 --- /dev/null +++ b/src/java/org/apache/hadoop/http/lib/package.html @@ -0,0 +1,30 @@ + + + + + +This package provides user-selectable (via configuration) classes that add +functionality to the web UI. They are configured as a list of classes in the +configuration parameter hadoop.http.filter.initializers. + +
    +
  • StaticUserWebFilter - An authorization plugin that makes all +users a static configured user. +
+ + diff --git a/src/test/core/org/apache/hadoop/http/lib/TestStaticUserWebFilter.java b/src/test/core/org/apache/hadoop/http/lib/TestStaticUserWebFilter.java new file mode 100644 index 0000000000..b7bf98cd50 --- /dev/null +++ b/src/test/core/org/apache/hadoop/http/lib/TestStaticUserWebFilter.java @@ -0,0 +1,80 @@ +/** + * 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.hadoop.http.lib; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.http.lib.StaticUserWebFilter.StaticUserFilter; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +public class TestStaticUserWebFilter { + private FilterConfig mockConfig(String username) { + FilterConfig mock = Mockito.mock(FilterConfig.class); + Mockito.doReturn(username).when(mock).getInitParameter( + StaticUserWebFilter.USERNAME_KEY); + return mock; + } + + @Test + public void testFilter() throws Exception { + FilterConfig config = mockConfig("myuser"); + StaticUserFilter suf = new StaticUserFilter(); + suf.init(config); + + ArgumentCaptor wrapperArg = + ArgumentCaptor.forClass(HttpServletRequestWrapper.class); + + FilterChain chain = mock(FilterChain.class); + + suf.doFilter(mock(HttpServletRequest.class), mock(ServletResponse.class), + chain); + + Mockito.verify(chain).doFilter(wrapperArg.capture(), Mockito.anyObject()); + + HttpServletRequestWrapper wrapper = wrapperArg.getValue(); + assertEquals("myuser", wrapper.getUserPrincipal().getName()); + assertEquals("myuser", wrapper.getRemoteUser()); + + suf.destroy(); + } + + @Test + public void testOldStyleConfiguration() { + Configuration conf = new Configuration(); + conf.set("dfs.web.ugi", "joe,group1,group2"); + assertEquals("joe", StaticUserWebFilter.getUsernameFromConf(conf)); + } + + @Test + public void testConfiguration() { + Configuration conf = new Configuration(); + conf.set(StaticUserWebFilter.USERNAME_KEY, "joe"); + assertEquals("joe", StaticUserWebFilter.getUsernameFromConf(conf)); + } + +}