HADOOP-16287. Implement ProxyUserAuthenticationFilter for web protocol impersonation.

Contributed by Prabhu Joseph
This commit is contained in:
Eric Yang 2019-05-23 11:36:32 -04:00
parent a771e2a638
commit ea0b1d8fba
6 changed files with 340 additions and 0 deletions

View File

@ -160,6 +160,16 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</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>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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-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 |
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.

View File

@ -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");
}
}