浏览代码

HADOOP-16287. Implement ProxyUserAuthenticationFilter for web protocol impersonation.
Contributed by Prabhu Joseph

Eric Yang 6 年之前
父节点
当前提交
ea0b1d8fba

+ 10 - 0
hadoop-common-project/hadoop-common/pom.xml

@@ -160,6 +160,16 @@
       <artifactId>junit</artifactId>
       <artifactId>junit</artifactId>
       <scope>test</scope>
       <scope>test</scope>
     </dependency>
     </dependency>
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.glassfish.grizzly</groupId>
+      <artifactId>grizzly-http-servlet</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
     <dependency>
       <groupId>commons-beanutils</groupId>
       <groupId>commons-beanutils</groupId>
       <artifactId>commons-beanutils</artifactId>
       <artifactId>commons-beanutils</artifactId>

+ 115 - 0
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/authentication/server/ProxyUserAuthenticationFilter.java

@@ -0,0 +1,115 @@
+/**
+ * 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. See accompanying LICENSE file.
+ */
+package org.apache.hadoop.security.authentication.server;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.authorize.AuthorizationException;
+import org.apache.hadoop.security.authorize.ProxyUsers;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.util.HttpExceptionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Enumeration;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * AuthenticationFilter which adds support to perform operations
+ * using end user instead of proxy user. Fetches the end user from
+ * doAs Query Parameter.
+ */
+public class ProxyUserAuthenticationFilter extends AuthenticationFilter {
+
+  private static final Logger LOG = LoggerFactory.getLogger(
+      ProxyUserAuthenticationFilter.class);
+
+  private static final String DO_AS = "doAs";
+  public static final String PROXYUSER_PREFIX = "proxyuser";
+
+  @Override
+  public void init(FilterConfig filterConfig) throws ServletException {
+    Configuration conf = getProxyuserConfiguration(filterConfig);
+    ProxyUsers.refreshSuperUserGroupsConfiguration(conf, PROXYUSER_PREFIX);
+    super.init(filterConfig);
+  }
+
+  @Override
+  protected void doFilter(FilterChain filterChain, HttpServletRequest request,
+      HttpServletResponse response) throws IOException, ServletException {
+
+    String doAsUser = request.getParameter(DO_AS);
+    if (doAsUser != null && !doAsUser.equals(request.getRemoteUser())) {
+      LOG.debug("doAsUser = {}, RemoteUser = {} , RemoteAddress = {} ",
+          doAsUser, request.getRemoteUser(), request.getRemoteAddr());
+      UserGroupInformation requestUgi = (request.getUserPrincipal() != null) ?
+          UserGroupInformation.createRemoteUser(request.getRemoteUser())
+          : null;
+      if (requestUgi != null) {
+        requestUgi = UserGroupInformation.createProxyUser(doAsUser,
+            requestUgi);
+        try {
+          ProxyUsers.authorize(requestUgi, request.getRemoteAddr());
+
+          final UserGroupInformation ugiF = requestUgi;
+          request = new HttpServletRequestWrapper(request) {
+            @Override
+            public String getRemoteUser() {
+              return ugiF.getShortUserName();
+            }
+
+            @Override
+            public Principal getUserPrincipal() {
+              return new Principal() {
+                @Override
+                public String getName() {
+                  return ugiF.getUserName();
+                }
+              };
+            }
+          };
+          LOG.debug("Proxy user Authentication successful");
+        } catch (AuthorizationException ex) {
+          HttpExceptionUtils.createServletExceptionResponse(response,
+              HttpServletResponse.SC_FORBIDDEN, ex);
+          LOG.warn("Proxy user Authentication exception", ex);
+          return;
+        }
+      }
+    }
+    super.doFilter(filterChain, request, response);
+  }
+
+  protected Configuration getProxyuserConfiguration(FilterConfig filterConfig)
+      throws ServletException {
+    Configuration conf = new Configuration(false);
+    Enumeration<?> names = filterConfig.getInitParameterNames();
+    while (names.hasMoreElements()) {
+      String name = (String) names.nextElement();
+      if (name.startsWith(PROXYUSER_PREFIX + ".")) {
+        String value = filterConfig.getInitParameter(name);
+        conf.set(name, value);
+      }
+    }
+    return conf;
+  }
+
+}
+

+ 60 - 0
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/authentication/server/ProxyUserAuthenticationFilterInitializer.java

@@ -0,0 +1,60 @@
+/**
+ * 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.security.authentication.server;
+
+import java.util.Map;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.http.FilterContainer;
+import org.apache.hadoop.http.FilterInitializer;
+import org.apache.hadoop.security.AuthenticationFilterInitializer;
+import org.apache.hadoop.security.authorize.ProxyUsers;
+
+/**
+ * Filter initializer to initialize
+ * {@link ProxyUserAuthenticationFilter} which adds support
+ * to perform operations using end user instead of proxy user.
+ */
+public class ProxyUserAuthenticationFilterInitializer
+    extends FilterInitializer {
+
+  private String configPrefix;
+
+  public ProxyUserAuthenticationFilterInitializer() {
+    this.configPrefix = "hadoop.http.authentication.";
+  }
+
+  protected Map<String, String> createFilterConfig(Configuration conf) {
+    Map<String, String> filterConfig = AuthenticationFilterInitializer
+        .getFilterConfigMap(conf, configPrefix);
+    //Add proxy user configs
+    for (Map.Entry<String, String> entry : conf.getPropsWithPrefix(
+        ProxyUsers.CONF_HADOOP_PROXYUSER).entrySet()) {
+      filterConfig.put("proxyuser" + entry.getKey(), entry.getValue());
+    }
+    return filterConfig;
+  }
+
+  @Override
+  public void initFilter(FilterContainer container, Configuration conf) {
+    Map<String, String> filterConfig = createFilterConfig(conf);
+    container.addFilter("ProxyUserAuthenticationFilter",
+        ProxyUserAuthenticationFilter.class.getName(), filterConfig);
+  }
+
+}

+ 22 - 0
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/authentication/server/package-info.java

@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provides the server-side framework for authentication.
+ */
+package org.apache.hadoop.security.authentication.server;

+ 8 - 0
hadoop-common-project/hadoop-common/src/site/markdown/HttpAuthentication.md

@@ -64,3 +64,11 @@ Add org.apache.hadoop.security.HttpCrossOriginFilterInitializer to hadoop.http.f
 | hadoop.http.cross-origin.allowed-methods | `GET,POST,HEAD`                               | Comma separated list of methods that are allowed                                       |
 | hadoop.http.cross-origin.allowed-methods | `GET,POST,HEAD`                               | Comma separated list of methods that are allowed                                       |
 | hadoop.http.cross-origin.allowed-headers | `X-Requested-With,Content-Type,Accept,Origin` | Comma separated list of headers that are allowed                                       |
 | hadoop.http.cross-origin.allowed-headers | `X-Requested-With,Content-Type,Accept,Origin` | Comma separated list of headers that are allowed                                       |
 | hadoop.http.cross-origin.max-age         | `1800`                                        | Number of seconds a pre-flighted request can be cached                                 |
 | hadoop.http.cross-origin.max-age         | `1800`                                        | Number of seconds a pre-flighted request can be cached                                 |
+
+
+Trusted Proxy
+-------------
+Trusted Proxy adds support to perform operations using end user instead of proxy user. It fetches the end user from
+doAs query parameter. To enable Trusted Proxy, please set the following configuration parameter:
+
+Add org.apache.hadoop.security.authentication.server.ProxyUserAuthenticationFilterInitializer to hadoop.http.filter.initializers at the end in core-site.xml.

+ 125 - 0
hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/authentication/server/TestProxyUserAuthenticationFilter.java

@@ -0,0 +1,125 @@
+/**
+ * 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.security.authentication.server;
+
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import org.glassfish.grizzly.servlet.HttpServletResponseImpl;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+
+/**
+ * Test ProxyUserAuthenticationFilter with doAs Request Parameter.
+ */
+public class TestProxyUserAuthenticationFilter {
+
+  private String actualUser;
+
+  private static class DummyFilterConfig implements FilterConfig {
+    private final Map<String, String> map;
+
+    DummyFilterConfig(Map<String, String> map) {
+      this.map = map;
+    }
+
+    @Override
+    public String getFilterName() {
+      return "dummy";
+    }
+
+    @Override
+    public String getInitParameter(String param) {
+      return map.get(param);
+    }
+
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+      return Collections.enumeration(map.keySet());
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+      ServletContext context = Mockito.mock(ServletContext.class);
+      Mockito.when(context.getAttribute(
+          AuthenticationFilter.SIGNER_SECRET_PROVIDER_ATTRIBUTE))
+          .thenReturn(null);
+      return context;
+    }
+  }
+
+  private class HttpServletResponseForTest extends HttpServletResponseImpl {
+
+  }
+
+
+  @Test(timeout = 10000)
+  public void testFilter() throws Exception {
+    Map<String, String> params = new HashMap<String, String>();
+    params.put("proxyuser.knox.users", "testuser");
+    params.put("proxyuser.knox.hosts", "127.0.0.1");
+    params.put("type", "simple");
+
+    FilterConfig config = new DummyFilterConfig(params);
+
+    FilterChain chain = new FilterChain() {
+      @Override
+      public void doFilter(ServletRequest servletRequest,
+          ServletResponse servletResponse) {
+        HttpServletRequest request = (HttpServletRequest) servletRequest;
+        actualUser = request.getRemoteUser();
+      }
+    };
+
+    ProxyUserAuthenticationFilter testFilter =
+        new ProxyUserAuthenticationFilter();
+    testFilter.init(config);
+
+    HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+    Mockito.when(request.getRemoteUser()).thenReturn("knox");
+    Mockito.when(request.getParameter("doAs")).thenReturn("testuser");
+    Mockito.when(request.getRemoteAddr()).thenReturn("127.0.0.1");
+    Mockito.when(request.getUserPrincipal()).thenReturn(new Principal() {
+      @Override
+      public String getName() {
+        return "knox@EXAMPLE.COM";
+      }
+    });
+
+    HttpServletResponseForTest response = new HttpServletResponseForTest();
+
+    testFilter.doFilter(chain, request, response);
+
+    assertThat(actualUser).isEqualTo("testuser");
+  }
+
+
+}