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