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