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

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


The following commit(s) were added to refs/heads/master by this push:
     new 2a12710  KNOX-1740 - Add Trusted Proxy Support to Knox (#106)
2a12710 is described below

commit 2a127101a4a7ba3ae3f37039eb36403d5d46922e
Author: Robert Levas <[email protected]>
AuthorDate: Tue Jul 2 10:03:10 2019 -0400

    KNOX-1740 - Add Trusted Proxy Support to Knox (#106)
---
 gateway-admin-ui/package-lock.json                 |  41 ++----
 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 +
 14 files changed, 618 insertions(+), 97 deletions(-)

diff --git a/gateway-admin-ui/package-lock.json 
b/gateway-admin-ui/package-lock.json
index 15be0bb..c961cdb 100644
--- a/gateway-admin-ui/package-lock.json
+++ b/gateway-admin-ui/package-lock.json
@@ -2838,8 +2838,7 @@
         "ansi-regex": {
           "version": "2.1.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "aproba": {
           "version": "1.2.0",
@@ -2860,14 +2859,12 @@
         "balanced-match": {
           "version": "1.0.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -2882,20 +2879,17 @@
         "code-point-at": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "concat-map": {
           "version": "0.0.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -3012,8 +3006,7 @@
         "inherits": {
           "version": "2.0.3",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "ini": {
           "version": "1.3.5",
@@ -3025,7 +3018,6 @@
           "version": "1.0.0",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -3040,7 +3032,6 @@
           "version": "3.0.4",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
@@ -3048,14 +3039,12 @@
         "minimist": {
           "version": "0.0.8",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "minipass": {
           "version": "2.2.4",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.1",
             "yallist": "^3.0.0"
@@ -3074,7 +3063,6 @@
           "version": "0.5.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -3155,8 +3143,7 @@
         "number-is-nan": {
           "version": "1.0.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -3168,7 +3155,6 @@
           "version": "1.4.0",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -3254,8 +3240,7 @@
         "safe-buffer": {
           "version": "5.1.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -3291,7 +3276,6 @@
           "version": "1.0.2",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -3311,7 +3295,6 @@
           "version": "3.0.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -3355,14 +3338,12 @@
         "wrappy": {
           "version": "1.0.2",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "yallist": {
           "version": "3.0.2",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         }
       }
     },
diff --git a/gateway-provider-security-hadoopauth/pom.xml 
b/gateway-provider-security-hadoopauth/pom.xml
index f8210d1..a1bfa22 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;
+  }
 }

Reply via email to