You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by kr...@apache.org on 2019/07/02 14:07:25 UTC

[knox] branch v1.3.0 updated: KNOX-1740 - Add Trusted Proxy Support to Knox (#106)

This is an automated email from the ASF dual-hosted git repository.

krisden pushed a commit to branch v1.3.0
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/v1.3.0 by this push:
     new 13f1411  KNOX-1740 - Add Trusted Proxy Support to Knox (#106)
13f1411 is described below

commit 13f141188185b872a3f522b3582574828922b8cf
Author: Robert Levas <rl...@apache.org>
AuthorDate: Tue Jul 2 10:03:10 2019 -0400

    KNOX-1740 - Add Trusted Proxy Support to Knox (#106)
---
 gateway-provider-security-hadoopauth/pom.xml       |  13 +-
 .../gateway/hadoopauth/HadoopAuthMessages.java     |  10 ++
 .../hadoopauth/filter/HadoopAuthFilter.java        | 156 +++++++++++++++++++++
 .../hadoopauth/filter/HadoopAuthFilterTest.java    | 140 +++++++++++-------
 .../gateway/config/impl/GatewayConfigImpl.java     |  14 ++
 .../apache/knox/gateway/shell/AbstractRequest.java |  25 +++-
 .../apache/knox/gateway/shell/knox/token/Get.java  |  37 +++--
 .../knox/gateway/shell/knox/token/Token.java       |   8 +-
 .../knox/gateway/shell/AbstractRequestTest.java    |  90 ++++++++++++
 .../knox/gateway/shell/knox/token/GetTest.java     |  79 +++++++++++
 .../knox/gateway/shell/knox/token/TokenTest.java   |  79 +++++++++++
 .../apache/knox/gateway/config/GatewayConfig.java  |  17 ++-
 .../org/apache/knox/gateway/GatewayTestConfig.java |   6 +
 13 files changed, 607 insertions(+), 67 deletions(-)

diff --git a/gateway-provider-security-hadoopauth/pom.xml b/gateway-provider-security-hadoopauth/pom.xml
index c138e28..f18ee38 100755
--- a/gateway-provider-security-hadoopauth/pom.xml
+++ b/gateway-provider-security-hadoopauth/pom.xml
@@ -66,5 +66,16 @@
             <artifactId>gateway-test-utils</artifactId>
             <scope>test</scope>
         </dependency>
-    </dependencies>
+
+        <dependency>
+            <groupId>org.apache.hadoop</groupId>
+            <artifactId>hadoop-common</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.knox</groupId>
+            <artifactId>gateway-provider-security-pac4j</artifactId>
+            <scope>test</scope>
+        </dependency>
+      </dependencies>
 </project>
diff --git a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/HadoopAuthMessages.java b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/HadoopAuthMessages.java
index 19b8c6c..c7953c4 100755
--- a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/HadoopAuthMessages.java
+++ b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/HadoopAuthMessages.java
@@ -20,9 +20,19 @@ package org.apache.knox.gateway.hadoopauth;
 import org.apache.knox.gateway.i18n.messages.Message;
 import org.apache.knox.gateway.i18n.messages.MessageLevel;
 import org.apache.knox.gateway.i18n.messages.Messages;
+import org.apache.knox.gateway.i18n.messages.StackTrace;
 
 @Messages(logger="org.apache.knox.gateway.provider.global.hadoopauth")
 public interface HadoopAuthMessages {
   @Message( level = MessageLevel.DEBUG, text = "Hadoop Authentication Asserted Principal: {0}" )
   void hadoopAuthAssertedPrincipal(String name);
+
+  @Message( level = MessageLevel.DEBUG, text = "doAsUser = {0}, RemoteUser = {1} , RemoteAddress = {2}" )
+  void hadoopAuthDoAsUser(String doAsUser, String remoteUser, String remoteAddr);
+
+  @Message( level = MessageLevel.DEBUG, text = "Proxy user Authentication successful" )
+  void hadoopAuthProxyUserSuccess();
+
+  @Message( level = MessageLevel.DEBUG, text = "Proxy user Authentication failed: {0}" )
+  void hadoopAuthProxyUserFailed(@StackTrace Throwable t);
 }
diff --git a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java
index 8d41dc2..b3a82e1 100755
--- a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java
+++ b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java
@@ -17,17 +17,36 @@
  */
 package org.apache.knox.gateway.hadoopauth.filter;
 
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.authorize.AuthorizationException;
+import org.apache.hadoop.security.authorize.ProxyUsers;
+import org.apache.hadoop.util.HttpExceptionUtils;
 import org.apache.knox.gateway.GatewayServer;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.hadoopauth.HadoopAuthMessages;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 import org.apache.knox.gateway.services.GatewayServices;
 import org.apache.knox.gateway.services.ServiceType;
 import org.apache.knox.gateway.services.security.AliasService;
 import org.apache.knox.gateway.services.security.AliasServiceException;
 
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Locale;
 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.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
 
 /*
  * see http://hadoop.apache.org/docs/current/hadoop-auth/Configuration.html
@@ -51,6 +70,13 @@ import javax.servlet.ServletException;
 public class HadoopAuthFilter extends
     org.apache.hadoop.security.authentication.server.AuthenticationFilter {
 
+  private static final String QUERY_PARAMETER_DOAS = "doAs";
+  private static final String PROXYUSER_PREFIX = "hadoop.proxyuser";
+
+  private static final HadoopAuthMessages LOG = MessagesFactory.get(HadoopAuthMessages.class);
+
+  private final Set<String> ignoreDoAs = new HashSet<>();
+
   @Override
   protected Properties getConfiguration(String configPrefix, FilterConfig filterConfig) throws ServletException {
     GatewayServices services = GatewayServer.getGatewayServices();
@@ -59,6 +85,136 @@ public class HadoopAuthFilter extends
     return getConfiguration(aliasService, configPrefix, filterConfig);
   }
 
+  @Override
+  public void init(FilterConfig filterConfig) throws ServletException {
+    Configuration conf = getProxyuserConfiguration(filterConfig);
+    ProxyUsers.refreshSuperUserGroupsConfiguration(conf, PROXYUSER_PREFIX);
+
+    Collection<String> ignoredServices = null;
+
+    // Look for GatewayConfig.PROXYUSER_SERVICES_IGNORE_DOAS value in the filter context, which was created
+    // using the relevant topology file...
+    String configValue = filterConfig.getInitParameter(GatewayConfig.PROXYUSER_SERVICES_IGNORE_DOAS);
+    if (configValue != null) {
+      configValue = configValue.trim();
+      if (!configValue.isEmpty()) {
+        ignoredServices = Arrays.asList(configValue.toLowerCase(Locale.ROOT).split("\\s*,\\s*"));
+      }
+    }
+
+    // If not set in the topology, look for GatewayConfig.PROXYUSER_SERVICES_IGNORE_DOAS in the
+    // gateway site context
+    if (ignoredServices == null) {
+      Object attributeValue = filterConfig.getServletContext().getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);
+      if (attributeValue instanceof GatewayConfig) {
+        ignoredServices = ((GatewayConfig) attributeValue).getServicesToIgnoreDoAs();
+      }
+    }
+
+    if (ignoredServices != null) {
+      ignoreDoAs.addAll(ignoredServices);
+    }
+
+    super.init(filterConfig);
+  }
+
+  @Override
+  protected void doFilter(FilterChain filterChain, HttpServletRequest request,
+                          HttpServletResponse response) throws IOException, ServletException {
+
+    /*
+     * If impersonation is not ignored for the authenticated user, attempt to set a proxied user if
+     * one was specified in the doAs query parameter.  A comma-delimited list of services/users to
+     * be ignored may be set in either the relevant topology file or the Gateway's gateway-site
+     * configuration file using a property named `gateway.proxyuser.services.ignore.doas`
+     *
+     * If setting a proxy user, proper authorization checks are made to ensure the authenticated user
+     * (proxy user) is allowed to set specified proxied user. It is expected that the relevant
+     * topology file has the required hadoop.proxyuser configurations set.
+     */
+    if (!ignoreDoAs(request.getRemoteUser())) {
+      String doAsUser = request.getParameter(QUERY_PARAMETER_DOAS);
+      if (doAsUser != null && !doAsUser.equals(request.getRemoteUser())) {
+        LOG.hadoopAuthDoAsUser(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 ugiF::getUserName;
+              }
+            };
+
+            LOG.hadoopAuthProxyUserSuccess();
+          } catch (AuthorizationException ex) {
+            HttpExceptionUtils.createServletExceptionResponse(response,
+                HttpServletResponse.SC_FORBIDDEN, ex);
+            LOG.hadoopAuthProxyUserFailed(ex);
+            return;
+          }
+        }
+      }
+    }
+
+    super.doFilter(filterChain, request, response);
+  }
+
+  /**
+   * Tests if the authenticated user/service has impersonation enabled based on previously calculated
+   * data (see {@link #init(FilterConfig)}.
+   * <p>
+   * By default, impersonation is enabled for all.  However if an entry in the pre-calculated data
+   * declares that is it disabled, then return false.
+   *
+   * @param userName the user name to test
+   * @return <code>true</code>, if impersonation is enabled for the relative principal; otherwise, <code>false</code>
+   */
+  boolean ignoreDoAs(String userName) {
+    // Return true if one the following conditions have been met:
+    // * the userPrincipal is null
+    // * the user principal exists on the ignoreDoAs set.
+    return (userName == null) || userName.isEmpty() || ignoreDoAs.contains(userName.toLowerCase(Locale.ROOT));
+  }
+
+  /**
+   * Return a {@link Configuration} instance with the proxy user (<code>hadoop.proxyuser.*</code>)
+   * properties set using parameter information from the filterConfig.
+   *
+   * @param filterConfig the {@link FilterConfig} to query
+   * @return a {@link Configuration}
+   */
+  private Configuration getProxyuserConfiguration(FilterConfig filterConfig) {
+    Configuration conf = new Configuration(false);
+
+    // Iterate through the init parameters of the filter configuration to add Hadoop proxyuser
+    // parameters to the configuration instance
+    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;
+  }
+
   // Visible for testing
   Properties getConfiguration(AliasService aliasService, String configPrefix,
                                         FilterConfig filterConfig) throws ServletException {
diff --git a/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java b/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java
index 20b924d..e9a6ef6 100644
--- a/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java
+++ b/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java
@@ -17,88 +17,122 @@
  */
 package org.apache.knox.gateway.hadoopauth.filter;
 
-import org.apache.knox.gateway.deploy.DeploymentContext;
-import org.apache.knox.gateway.services.ServiceType;
-import org.apache.knox.gateway.services.GatewayServices;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createMockBuilder;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.getCurrentArguments;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.services.security.AliasService;
-import org.apache.knox.gateway.services.security.impl.DefaultCryptoService;
 import org.apache.knox.gateway.topology.Topology;
-import org.easymock.EasyMock;
 import org.junit.Test;
 
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletContext;
-import java.util.Enumeration;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Properties;
 
-import static org.junit.Assert.assertEquals;
-
 public class HadoopAuthFilterTest {
   @Test
   public void testHadoopAuthFilterAliases() throws Exception {
     String aliasKey = "signature.secret";
     String aliasConfigKey = "${ALIAS=" + aliasKey + "}";
     String aliasValue = "password";
+    String clusterName = "Sample";
 
     Topology topology = new Topology();
-    topology.setName("Sample");
-
-    DeploymentContext context = EasyMock.createNiceMock(DeploymentContext.class);
-    EasyMock.expect(context.getTopology()).andReturn(topology).anyTimes();
-    EasyMock.replay(context);
+    topology.setName(clusterName);
 
-    String clusterName = context.getTopology().getName();
-
-    AliasService as = EasyMock.createNiceMock(AliasService.class);
-    EasyMock.expect(as.getPasswordFromAliasForCluster(clusterName, aliasKey))
-        .andReturn(aliasValue.toCharArray()).anyTimes();
-    EasyMock.replay(as);
-    DefaultCryptoService cryptoService = new DefaultCryptoService();
-    cryptoService.setAliasService(as);
-
-    GatewayServices gatewayServices = EasyMock.createNiceMock(GatewayServices.class);
-    EasyMock.expect(gatewayServices.getService(ServiceType.CRYPTO_SERVICE)).andReturn(cryptoService).anyTimes();
-
-    HadoopAuthFilter hadoopAuthFilter = new HadoopAuthFilter();
+    AliasService as = createMock(AliasService.class);
+    expect(as.getPasswordFromAliasForCluster(clusterName, aliasKey))
+        .andReturn(aliasValue.toCharArray()).atLeastOnce();
 
     String configPrefix = "hadoop.auth.config.";
 
-    Properties props = new Properties();
+    Map<String, String> props = new HashMap<>();
     props.put("clusterName", clusterName);
     props.put(configPrefix + "signature.secret", aliasConfigKey);
     props.put(configPrefix + "test", "abc");
 
-    FilterConfig filterConfig = new HadoopAuthTestFilterConfig(props);
-    Properties configuration = hadoopAuthFilter.getConfiguration(as, configPrefix, filterConfig);
-    assertEquals(aliasValue, configuration.getProperty(aliasKey));
-    assertEquals("abc", configuration.getProperty("test"));
-  }
+    FilterConfig filterConfig = createMock(FilterConfig.class);
+    expect(filterConfig.getInitParameter(anyString()))
+        .andAnswer(() -> props.get(getCurrentArguments()[0].toString()))
+        .atLeastOnce();
+    expect(filterConfig.getInitParameterNames()).andReturn(Collections.enumeration(props.keySet())).atLeastOnce();
 
-  private static class HadoopAuthTestFilterConfig implements FilterConfig {
-    Properties props;
+    replay(filterConfig, as);
 
-    HadoopAuthTestFilterConfig(Properties props) {
-      this.props = props;
-    }
+    HadoopAuthFilter hadoopAuthFilter = new HadoopAuthFilter();
 
-    @Override
-    public String getFilterName() {
-      return null;
-    }
+    Properties configuration = hadoopAuthFilter.getConfiguration(as, configPrefix, filterConfig);
+    assertEquals(aliasValue, configuration.getProperty(aliasKey));
+    assertEquals("abc", configuration.getProperty("test"));
 
-    @Override
-    public ServletContext getServletContext() {
-      return null;
-    }
+    verify(filterConfig, as);
+  }
 
-    @Override
-    public String getInitParameter(String name) {
-      return props.getProperty(name, null);
-    }
+  @Test
+  public void testHadoopAuthFilterIgnoreDoAs() throws Exception {
+    Topology topology = new Topology();
+    topology.setName("Sample");
 
-    @Override
-    public Enumeration<String> getInitParameterNames() {
-      return (Enumeration<String>)props.propertyNames();
-    }
+    ServletContext servletContext = createMock(ServletContext.class);
+    expect(servletContext.getAttribute("signer.secret.provider.object")).andReturn(null).atLeastOnce();
+
+    FilterConfig filterConfig = createMock(FilterConfig.class);
+    expect(filterConfig.getInitParameter("config.prefix"))
+        .andReturn("some.prefix")
+        .atLeastOnce();
+    expect(filterConfig.getInitParameterNames())
+        .andReturn(Collections.enumeration(Collections.singleton(GatewayConfig.PROXYUSER_SERVICES_IGNORE_DOAS)))
+        .atLeastOnce();
+    expect(filterConfig.getInitParameter(GatewayConfig.PROXYUSER_SERVICES_IGNORE_DOAS))
+        .andReturn("Knox, hdfs,TesT") // Spacing and case set on purpose
+        .atLeastOnce();
+    expect(filterConfig.getServletContext()).andReturn(servletContext).atLeastOnce();
+
+    Properties configProperties = createMock(Properties.class);
+    expect(configProperties.getProperty("signature.secret.file")).andReturn("signature.secret.file").atLeastOnce();
+    expect(configProperties.getProperty(anyString(), anyString())).andAnswer(() -> {
+      Object[] args = getCurrentArguments();
+
+      if ("type".equals(args[0])) {
+        return "simple"; // This is "simple", rather than "kerberos" to avoid the super class' init logic
+      } else {
+        return (String) args[1];
+      }
+    }).atLeastOnce();
+
+    HadoopAuthFilter hadoopAuthFilter = createMockBuilder(HadoopAuthFilter.class)
+        .addMockedMethod("getConfiguration", String.class, FilterConfig.class)
+        .withConstructor()
+        .createMock();
+    expect(hadoopAuthFilter.getConfiguration(eq("some.prefix."), eq(filterConfig)))
+        .andReturn(configProperties)
+        .atLeastOnce();
+
+    replay(filterConfig, configProperties, hadoopAuthFilter, servletContext);
+
+    hadoopAuthFilter.init(filterConfig);
+
+    assertTrue(hadoopAuthFilter.ignoreDoAs("knox"));
+    assertTrue(hadoopAuthFilter.ignoreDoAs("hdfs"));
+    assertTrue(hadoopAuthFilter.ignoreDoAs("test"));
+    assertTrue(hadoopAuthFilter.ignoreDoAs("TEST"));
+    assertTrue(hadoopAuthFilter.ignoreDoAs(null));
+    assertTrue(hadoopAuthFilter.ignoreDoAs(""));
+    assertFalse(hadoopAuthFilter.ignoreDoAs("hive"));
+    assertFalse(hadoopAuthFilter.ignoreDoAs("HivE"));
+
+    verify(filterConfig, configProperties, hadoopAuthFilter, servletContext);
   }
 }
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
index 2bb77cb..bda88c7 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
@@ -40,9 +40,11 @@ import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 
@@ -1081,4 +1083,16 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
       return new ArrayList<>();
     }
   }
+
+  @Override
+  public Set<String> getServicesToIgnoreDoAs() {
+    Set<String> set = new HashSet<>();
+    String value = get( PROXYUSER_SERVICES_IGNORE_DOAS );
+
+    if (value != null) {
+      set.addAll(Arrays.asList(value.trim().toLowerCase(Locale.ROOT).split("\\s*,\\s*")));
+    }
+
+    return set;
+  }
 }
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/AbstractRequest.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/AbstractRequest.java
index 48a2e05..ba2df30 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/AbstractRequest.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/AbstractRequest.java
@@ -34,11 +34,19 @@ import java.util.concurrent.Callable;
 import java.util.concurrent.Future;
 
 public abstract class AbstractRequest<T> {
+  private static final String PARAMETER_NAME_DOAS = "doAs";
 
-  private KnoxSession session;
+  private final KnoxSession session;
+
+  private final String doAsUser;
 
   protected AbstractRequest( KnoxSession session ) {
+    this(session, null);
+  }
+
+  protected AbstractRequest( KnoxSession session, String doAsUser ) {
     this.session = session;
+    this.doAsUser = doAsUser;
   }
 
   protected KnoxSession hadoop() {
@@ -57,7 +65,13 @@ public abstract class AbstractRequest<T> {
   }
 
   protected URIBuilder uri( String... parts ) throws URISyntaxException {
-    return new URIBuilder( session.base() + StringUtils.join( parts ) );
+    URIBuilder builder = new URIBuilder(session.base() + StringUtils.join(parts));
+
+    if(StringUtils.isNotEmpty(doAsUser)) {
+      builder.addParameter(PARAMETER_NAME_DOAS, doAsUser);
+    }
+
+    return builder;
   }
 
   protected void addQueryParam( URIBuilder uri, String name, Object value ) {
@@ -74,6 +88,10 @@ public abstract class AbstractRequest<T> {
 
   protected abstract Callable<T> callable();
 
+  public KnoxSession getSession() {
+    return session;
+  }
+
   public T now() throws KnoxShellException {
     try {
       return callable().call();
@@ -97,4 +115,7 @@ public abstract class AbstractRequest<T> {
     } );
   }
 
+  public String getDoAsUser() {
+    return doAsUser;
+  }
 }
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/knox/token/Get.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/knox/token/Get.java
index fe1aa77..ef996e6 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/knox/token/Get.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/knox/token/Get.java
@@ -18,6 +18,8 @@
 package org.apache.knox.gateway.shell.knox.token;
 
 import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.concurrent.Callable;
 
 import org.apache.knox.gateway.shell.AbstractRequest;
@@ -26,6 +28,7 @@ import org.apache.knox.gateway.shell.KnoxSession;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.utils.URIBuilder;
+import org.apache.knox.gateway.shell.KnoxShellException;
 
 /**
  * Acquire a Knox access token for token based authentication
@@ -34,18 +37,36 @@ import org.apache.http.client.utils.URIBuilder;
 public class Get {
   public static class Request extends AbstractRequest<Response> {
     Request(KnoxSession session) {
-      super(session);
+      this(session, null);
+    }
+
+    Request(KnoxSession session, String doAsUser) {
+      super(session, doAsUser);
+      try {
+        URIBuilder uri = uri(Token.SERVICE_PATH);
+        requestURI = uri.build();
+      } catch (URISyntaxException e) {
+        throw new KnoxShellException(e);
+      }
+    }
+
+    private URI requestURI;
+
+    private HttpGet httpGetRequest;
+
+    public URI getRequestURI() {
+      return requestURI;
+    }
+
+    public HttpGet getRequest() {
+      return httpGetRequest;
     }
 
     @Override
     protected Callable<Response> callable() {
-      return new Callable<Response>() {
-        @Override
-        public Response call() throws Exception {
-          URIBuilder uri = uri(Token.SERVICE_PATH);
-          HttpGet request = new HttpGet(uri.build());
-          return new Response(execute(request));
-        }
+      return () -> {
+        httpGetRequest = new HttpGet(requestURI);
+        return new Response(execute(httpGetRequest));
       };
     }
   }
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/knox/token/Token.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/knox/token/Token.java
index 35c4250..90478c0 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/knox/token/Token.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/knox/token/Token.java
@@ -23,7 +23,11 @@ public class Token {
 
   static String SERVICE_PATH = "/knoxtoken/api/v1/token";
 
-  public static Get.Request get( KnoxSession session ) {
-    return new Get.Request( session );
+  public static Get.Request get(KnoxSession session) {
+    return new Get.Request(session);
+  }
+
+  public static Get.Request get(KnoxSession session, String doAsUser) {
+    return new Get.Request(session, doAsUser);
   }
 }
diff --git a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/AbstractRequestTest.java b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/AbstractRequestTest.java
new file mode 100644
index 0000000..0864cf8
--- /dev/null
+++ b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/AbstractRequestTest.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.knox.gateway.shell;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createMockBuilder;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import org.apache.commons.lang3.StringUtils;
+import org.easymock.IMockBuilder;
+import org.junit.Test;
+
+import java.net.URISyntaxException;
+
+public class AbstractRequestTest {
+  @Test
+  public void testBuildUrlWithNoDoAs() throws Exception {
+    testBuildURL(false, null);
+  }
+
+  @Test
+  public void testBuildUrlWithNullDoAs() throws Exception {
+    testBuildURL(true, null);
+  }
+
+  @Test
+  public void testBuildUrlWithEmptyDoAs() throws Exception {
+    testBuildURL(true, "");
+  }
+
+  @Test
+  public void testBuildUrlWithDoAs() throws Exception {
+    testBuildURL(true, "userA");
+  }
+
+  private void testBuildURL(boolean setDoAsUser, String doAsUser) throws URISyntaxException {
+    KnoxSession knoxSession = createMock(KnoxSession.class);
+    expect(knoxSession.base()).andReturn("http://localhost/base").atLeastOnce();
+    replay(knoxSession);
+
+    IMockBuilder<AbstractRequest> builder = createMockBuilder(AbstractRequest.class);
+
+    if (setDoAsUser) {
+      builder.withConstructor(KnoxSession.class, String.class);
+      builder.withArgs(knoxSession, doAsUser);
+    } else {
+      builder.withConstructor(KnoxSession.class);
+      builder.withArgs(knoxSession);
+    }
+
+    AbstractRequest<?> request = builder.createMock();
+    replay(request);
+
+    if (setDoAsUser) {
+      assertEquals(doAsUser, request.getDoAsUser());
+    } else {
+      assertNull(request.getDoAsUser());
+    }
+
+    if (setDoAsUser && StringUtils.isNotEmpty(doAsUser)) {
+      assertEquals("http://localhost/base/test?doAs=" + doAsUser, request.uri("/test").toString());
+    } else {
+      assertEquals("http://localhost/base/test", request.uri("/test").toString());
+    }
+
+    assertSame(knoxSession, request.getSession());
+
+    verify(knoxSession, request);
+  }
+}
diff --git a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/knox/token/GetTest.java b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/knox/token/GetTest.java
new file mode 100644
index 0000000..8778b75
--- /dev/null
+++ b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/knox/token/GetTest.java
@@ -0,0 +1,79 @@
+/*
+    * 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.knox.gateway.shell.knox.token;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.knox.gateway.shell.KnoxSession;
+import org.junit.Test;
+
+public class GetTest {
+
+  @Test
+  public void testGetRequestWithNoDoAs() {
+    testGetRequest(false, null);
+  }
+
+  @Test
+  public void testGetRequestWithNullDoAs() {
+    testGetRequest(true, null);
+  }
+
+  @Test
+  public void testGetRequestWithEmptyDoAs() {
+    testGetRequest(true, "");
+  }
+
+  @Test
+  public void testGetRequestWithDoAs() {
+    testGetRequest(true, "userA");
+  }
+
+  private void testGetRequest(boolean setDoAsUser, String doAsUser) {
+    KnoxSession knoxSession = createMock(KnoxSession.class);
+    expect(knoxSession.base()).andReturn("http://localhost/base").atLeastOnce();
+    replay(knoxSession);
+
+    Get.Request request = (setDoAsUser)
+        ? new Get.Request(knoxSession, doAsUser)
+        : new Get.Request(knoxSession);
+
+    if (setDoAsUser) {
+      assertEquals(doAsUser, request.getDoAsUser());
+    } else {
+      assertNull(request.getDoAsUser());
+    }
+
+    if (setDoAsUser && StringUtils.isNotEmpty(doAsUser)) {
+      assertEquals("http://localhost/base/knoxtoken/api/v1/token?doAs=" + doAsUser, request.getRequestURI().toString());
+    } else {
+      assertEquals("http://localhost/base/knoxtoken/api/v1/token", request.getRequestURI().toString());
+    }
+
+    assertSame(knoxSession, request.getSession());
+
+    verify(knoxSession);
+  }
+}
\ No newline at end of file
diff --git a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/knox/token/TokenTest.java b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/knox/token/TokenTest.java
new file mode 100644
index 0000000..49cb998
--- /dev/null
+++ b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/knox/token/TokenTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.knox.gateway.shell.knox.token;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.knox.gateway.shell.KnoxSession;
+import org.junit.Test;
+
+public class TokenTest {
+
+  @Test
+  public void testTokenWithNoDoAs() {
+    testToken(false, null);
+  }
+
+  @Test
+  public void testTokenWithNullDoAs() {
+    testToken(true, null);
+  }
+
+  @Test
+  public void testTokenWithEmptyDoAs() {
+    testToken(true, "");
+  }
+
+  @Test
+  public void testTokenWithDoAs() {
+    testToken(true, "userA");
+  }
+
+  private void testToken(boolean setDoAsUser, String doAsUser) {
+    KnoxSession knoxSession = createMock(KnoxSession.class);
+    expect(knoxSession.base()).andReturn("http://localhost/base").atLeastOnce();
+    replay(knoxSession);
+
+    Get.Request request = (setDoAsUser)
+        ? Token.get(knoxSession, doAsUser)
+        : Token.get(knoxSession);
+
+    if (setDoAsUser) {
+      assertEquals(doAsUser, request.getDoAsUser());
+    } else {
+      assertNull(request.getDoAsUser());
+    }
+
+    if (setDoAsUser && StringUtils.isNotEmpty(doAsUser)) {
+      assertEquals("http://localhost/base/knoxtoken/api/v1/token?doAs=" + doAsUser, request.getRequestURI().toString());
+    } else {
+      assertEquals("http://localhost/base/knoxtoken/api/v1/token", request.getRequestURI().toString());
+    }
+
+    assertSame(knoxSession, request.getSession());
+
+    verify(knoxSession);
+  }
+}
\ No newline at end of file
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
index 5de18b6..790e6b2 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
@@ -21,6 +21,7 @@ import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 public interface GatewayConfig {
 
@@ -95,6 +96,8 @@ public interface GatewayConfig {
   String REMOTE_CONFIG_REGISTRY_USE_KEYTAB = "useKeytab";
   String REMOTE_CONFIG_REGISTRY_USE_TICKET_CACHE = "useTicketCache";
 
+  String PROXYUSER_SERVICES_IGNORE_DOAS = "gateway.proxyuser.services.ignore.doas";
+
   /**
    * The location of the gateway configuration.
    * Subdirectories will be: topologies
@@ -617,5 +620,17 @@ public interface GatewayConfig {
    */
   List<String> getXForwardContextAppendServices();
 
-
+  /**
+   * Returns a set of service principal names that indicate which services to ignore doAs requests.
+   * <p>
+   * If a service in the returned set sends a Kerberos-authenticated request to the Gateway, the doAs
+   * query parameter is to be ignored; thus leaving the authenticated user details intact.
+   * <p>
+   * If the (authenticated) service is not authorized to set the specified proxy user (see information
+   * related to hadoop.proxyuser.... properties) an error will not be returned since the request to
+   * impersonate users is to be ignored.
+   *
+   * @return a set of service principal names that indicate which services to ignore doAs request
+   */
+  Set<String> getServicesToIgnoreDoAs();
 }
diff --git a/gateway-test-release-utils/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java b/gateway-test-release-utils/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
index 951d173..aad1271 100644
--- a/gateway-test-release-utils/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
+++ b/gateway-test-release-utils/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
@@ -32,6 +32,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class GatewayTestConfig extends Configuration implements GatewayConfig {
@@ -759,4 +760,9 @@ public class GatewayTestConfig extends Configuration implements GatewayConfig {
   public List<String> getXForwardContextAppendServices() {
     return null;
   }
+
+  @Override
+  public Set<String> getServicesToIgnoreDoAs() {
+    return null;
+  }
 }