This is an automated email from the ASF dual-hosted git repository.
smolnar 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 ebac40dd4 KNOX-2831 - Make Hadoop impersonation work across topologies
and roles with different proxyuser configs (#660)
ebac40dd4 is described below
commit ebac40dd4992d0e7fd7bd1ebf622a041dad2679c
Author: Sandor Molnar <[email protected]>
AuthorDate: Fri Oct 28 08:50:11 2022 +0200
KNOX-2831 - Make Hadoop impersonation work across topologies and roles with
different proxyuser configs (#660)
---
.../gateway/hadoopauth/HadoopAuthMessages.java | 3 -
.../hadoopauth/filter/HadoopAuthFilter.java | 15 ++--
.../gateway/service/knoxtoken/TokenResource.java | 9 +--
.../service/knoxtoken/TokenServiceMessages.java | 2 -
.../deploy/TokenServiceDeploymentContributor.java | 4 +-
.../knoxtoken/TokenServiceResourceTest.java | 1 +
.../knox/gateway/i18n/GatewaySpiMessages.java | 3 +
.../apache/knox/gateway/util/AuthFilterUtils.java | 87 ++++++++++++++++------
8 files changed, 81 insertions(+), 43 deletions(-)
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 2b493a2da..98249a398 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
@@ -51,7 +51,4 @@ public interface HadoopAuthMessages {
@Message(level=MessageLevel.WARN, text="{1} alias is NOT stored on neither
topology ({0}) nor gateway levels.")
void noAliasStored(String cluster, String alias);
- @Message(level=MessageLevel.DEBUG, text="Refreshing proxyuser config in {0}
topology with prefix {1} and config {2}")
- void refreshProxyuserConfig(String topology, String prefix, String
properties);
-
}
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 44c202fff..edc2d3e88 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,9 +17,7 @@
*/
package org.apache.knox.gateway.hadoopauth.filter;
-import org.apache.hadoop.conf.Configuration;
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.audit.api.Action;
@@ -33,6 +31,7 @@ import
org.apache.knox.gateway.audit.log4j.audit.AuditConstants;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.filter.AbstractGatewayFilter;
import org.apache.knox.gateway.hadoopauth.HadoopAuthMessages;
+import
org.apache.knox.gateway.hadoopauth.deploy.HadoopAuthDeploymentContributor;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import
org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter;
import org.apache.knox.gateway.security.PrimaryPrincipal;
@@ -103,6 +102,7 @@ public class HadoopAuthFilter extends
private final Set<String> ignoreDoAs = new HashSet<>();
private JWTFederationFilter jwtFilter;
private Set<String> unAuthenticatedPaths = new HashSet<>(20);
+ private String topologyName;
@Override
protected Properties getConfiguration(String configPrefix, FilterConfig
filterConfig) throws ServletException {
@@ -114,13 +114,8 @@ public class HadoopAuthFilter extends
@Override
public void init(FilterConfig filterConfig) throws ServletException {
- final String topologyName = (String)
filterConfig.getInitParameter("clusterName");
- // Return a {@link Configuration} instance with the proxy user
- // (<code>hadoop.proxyuser.*</code>) properties set using parameter
information
- // from the filterConfig.
- final Configuration conf =
AuthFilterUtils.getProxyUserConfiguration(filterConfig, PROXYUSER_PREFIX);
- ProxyUsers.refreshSuperUserGroupsConfiguration(conf, PROXYUSER_PREFIX);
- LOG.refreshProxyuserConfig(topologyName, PROXYUSER_PREFIX,
conf.getPropsWithPrefix(PROXYUSER_PREFIX).toString());
+ this.topologyName = (String) filterConfig.getInitParameter("clusterName");
+ AuthFilterUtils.refreshSuperUserGroupsConfiguration(filterConfig,
PROXYUSER_PREFIX, topologyName, HadoopAuthDeploymentContributor.NAME);
Collection<String> ignoredServices = null;
@@ -210,7 +205,7 @@ public class HadoopAuthFilter extends
LOG.hadoopAuthDoAsUser(doAsUser, remoteUser, request.getRemoteAddr());
if (request.getUserPrincipal() != null) {
try {
- proxyRequest = AuthFilterUtils.getProxyRequest(request, doAsUser);
+ proxyRequest = AuthFilterUtils.getProxyRequest(request, doAsUser,
topologyName, HadoopAuthDeploymentContributor.NAME);
LOG.hadoopAuthProxyUserSuccess();
} catch (AuthorizationException ex) {
HttpExceptionUtils.createServletExceptionResponse(response,
HttpServletResponse.SC_FORBIDDEN, ex);
diff --git
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
index 5c2fd4ca8..ac3d5d0b1 100644
---
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
+++
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
@@ -61,13 +61,12 @@ import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.util.ByteUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
-import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.authorize.AuthorizationException;
-import org.apache.hadoop.security.authorize.ProxyUsers;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.security.GroupPrincipal;
import org.apache.knox.gateway.security.SubjectUtils;
+import
org.apache.knox.gateway.service.knoxtoken.deploy.TokenServiceDeploymentContributor;
import org.apache.knox.gateway.services.ServiceType;
import org.apache.knox.gateway.services.GatewayServices;
import org.apache.knox.gateway.services.ServiceLifecycleException;
@@ -325,9 +324,7 @@ public class TokenResource {
// refreshing Hadoop ProxyUser groups config only makes sense if token
state management is turned on
// and impersonation is enabled
if (impersonationEnabled) {
- final Configuration conf =
AuthFilterUtils.getProxyUserConfiguration(context, PROXYUSER_PREFIX);
- ProxyUsers.refreshSuperUserGroupsConfiguration(conf, PROXYUSER_PREFIX);
- log.refreshProxyuserConfig(topologyName, PROXYUSER_PREFIX,
conf.getPropsWithPrefix(PROXYUSER_PREFIX).toString());
+ AuthFilterUtils.refreshSuperUserGroupsConfiguration(context,
PROXYUSER_PREFIX, getTopologyName(), TokenServiceDeploymentContributor.ROLE);
}
}
setTokenStateServiceStatusMap();
@@ -728,7 +725,7 @@ public class TokenResource {
if (doAsUser != null && !doAsUser.equals(userName)) {
try {
//this call will authorize the doAs request
- AuthFilterUtils.authorizeImpersonationRequest(request, doAsUser);
+ AuthFilterUtils.authorizeImpersonationRequest(request, doAsUser,
getTopologyName(), TokenServiceDeploymentContributor.ROLE);
createdBy = userName;
userName = doAsUser;
log.tokenImpersonationSuccess(createdBy, doAsUser);
diff --git
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceMessages.java
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceMessages.java
index b0b4eb3f6..5d0254e35 100644
---
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceMessages.java
+++
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceMessages.java
@@ -96,6 +96,4 @@ public interface TokenServiceMessages {
@Message( level = MessageLevel.DEBUG, text = "Token impersonation failed:
{0}" )
void tokenImpersonationFailed(@StackTrace Throwable t);
- @Message(level=MessageLevel.DEBUG, text="Refreshing proxyuser config in {0}
topology with prefix {1} and config {2}")
- void refreshProxyuserConfig(String topology, String prefix, String
properties);
}
diff --git
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/deploy/TokenServiceDeploymentContributor.java
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/deploy/TokenServiceDeploymentContributor.java
index 830c66084..b46ddebd6 100644
---
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/deploy/TokenServiceDeploymentContributor.java
+++
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/deploy/TokenServiceDeploymentContributor.java
@@ -21,9 +21,11 @@ import
org.apache.knox.gateway.jersey.JerseyServiceDeploymentContributorBase;
public class TokenServiceDeploymentContributor extends
JerseyServiceDeploymentContributorBase {
+ public static final String ROLE = "KNOXTOKEN";
+
@Override
public String getRole() {
- return "KNOXTOKEN";
+ return ROLE;
}
@Override
diff --git
a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
index 64db322e8..e61c93ad3 100644
---
a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
+++
b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
@@ -155,6 +155,7 @@ public class TokenServiceResourceTest {
context = EasyMock.createNiceMock(ServletContext.class);
contextExpectations.forEach((key, value) ->
EasyMock.expect(context.getInitParameter(key)).andReturn(value).anyTimes());
EasyMock.expect(context.getInitParameterNames()).andReturn(Collections.enumeration(contextExpectations.keySet())).anyTimes();
+
EasyMock.expect(context.getAttribute("org.apache.knox.gateway.gateway.cluster")).andReturn("topology1").anyTimes();
request = EasyMock.createNiceMock(HttpServletRequest.class);
EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes();
Principal principal = EasyMock.createNiceMock(Principal.class);
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/i18n/GatewaySpiMessages.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/i18n/GatewaySpiMessages.java
index 023695fe3..48f6ca643 100644
---
a/gateway-spi/src/main/java/org/apache/knox/gateway/i18n/GatewaySpiMessages.java
+++
b/gateway-spi/src/main/java/org/apache/knox/gateway/i18n/GatewaySpiMessages.java
@@ -81,4 +81,7 @@ public interface GatewaySpiMessages {
@Message(level = MessageLevel.WARN, text = "Duplicated filter param key:
{0}")
void duplicatedFilterParamKey(String name);
+
+ @Message(level=MessageLevel.DEBUG, text="Creating impersonation provider in
{0} / {1} with prefix {2} and config {3}")
+ void createImpersonationProvider(String topology, String role, String
prefix, String properties);
}
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/util/AuthFilterUtils.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/util/AuthFilterUtils.java
index aeea2287d..08b82559f 100644
---
a/gateway-spi/src/main/java/org/apache/knox/gateway/util/AuthFilterUtils.java
+++
b/gateway-spi/src/main/java/org/apache/knox/gateway/util/AuthFilterUtils.java
@@ -17,11 +17,15 @@
*/
package org.apache.knox.gateway.util;
-import org.apache.commons.lang3.StringUtils;
-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 java.security.Principal;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
@@ -29,14 +33,22 @@ import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
-import java.security.Principal;
-import java.util.Enumeration;
-import java.util.Set;
-import java.util.StringTokenizer;
+import org.apache.commons.lang3.StringUtils;
+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.DefaultImpersonationProvider;
+import org.apache.hadoop.security.authorize.ImpersonationProvider;
+import org.apache.knox.gateway.i18n.GatewaySpiMessages;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
public class AuthFilterUtils {
public static final String DEFAULT_AUTH_UNAUTHENTICATED_PATHS_PARAM =
"/knoxtoken/api/v1/jwks.json";
+ private static final GatewaySpiMessages LOG =
MessagesFactory.get(GatewaySpiMessages.class);
+ private static final Map<String, Map<String, ImpersonationProvider>>
TOPOLOGY_IMPERSONATION_PROVIDERS = new ConcurrentHashMap<>();
+ private static final Lock refreshSuperUserGroupsLock = new ReentrantLock();
+
/**
* A helper method that checks whether request contains
* unauthenticated path
@@ -78,21 +90,21 @@ public class AuthFilterUtils {
}
}
- public static Configuration getProxyUserConfiguration(ServletContext
context, String prefix) {
+ public static void refreshSuperUserGroupsConfiguration(ServletContext
context, String prefix, String topologyName, String role) {
if (context == null) {
throw new IllegalArgumentException("Cannot get proxyuser configuration
from NULL context");
}
- return getProxyUserConfiguration(context, null, prefix);
+ refreshSuperUserGroupsConfiguration(context, null, prefix, topologyName,
role);
}
- public static Configuration getProxyUserConfiguration(FilterConfig
filterConfig, String prefix) {
+ public static void refreshSuperUserGroupsConfiguration(FilterConfig
filterConfig, String prefix, String topologyName, String role) {
if (filterConfig == null) {
throw new IllegalArgumentException("Cannot get proxyuser configuration
from NULL filter config");
}
- return getProxyUserConfiguration(null, filterConfig, prefix);
+ refreshSuperUserGroupsConfiguration(null, filterConfig, prefix,
topologyName, role);
}
- private static Configuration getProxyUserConfiguration(ServletContext
context, FilterConfig filterConfig, String prefix) {
+ private static void refreshSuperUserGroupsConfiguration(ServletContext
context, FilterConfig filterConfig, String prefix, String topologyName, String
role) {
final Configuration conf = new Configuration(false);
final Enumeration<?> names = context == null ?
filterConfig.getInitParameterNames() : context.getInitParameterNames();
if (names != null) {
@@ -105,13 +117,27 @@ public class AuthFilterUtils {
}
}
- return conf;
+ saveImpersonationProvider(prefix, topologyName, role, conf);
+ }
+
+ private static void saveImpersonationProvider(String prefix, String
topologyName, String role, final Configuration conf) {
+ refreshSuperUserGroupsLock.lock();
+ try {
+ final ImpersonationProvider impersonationProvider = new
DefaultImpersonationProvider();
+ impersonationProvider.setConf(conf);
+ impersonationProvider.init(prefix);
+ LOG.createImpersonationProvider(topologyName, role, prefix,
conf.getPropsWithPrefix(prefix + ".").toString());
+ TOPOLOGY_IMPERSONATION_PROVIDERS.putIfAbsent(topologyName, new
ConcurrentHashMap<String, ImpersonationProvider>());
+ TOPOLOGY_IMPERSONATION_PROVIDERS.get(topologyName).put(role,
impersonationProvider);
+ } finally {
+ refreshSuperUserGroupsLock.unlock();
+ }
}
- public static HttpServletRequest getProxyRequest(HttpServletRequest request,
String doAsUser) throws AuthorizationException {
+ public static HttpServletRequest getProxyRequest(HttpServletRequest request,
String doAsUser, String topologyName, String role) throws
AuthorizationException {
final UserGroupInformation remoteRequestUgi = getRemoteRequestUgi(request,
doAsUser);
if (remoteRequestUgi != null) {
- authorizeImpersonationRequest(request, remoteRequestUgi);
+ authorizeImpersonationRequest(request, remoteRequestUgi, topologyName,
role);
return new HttpServletRequestWrapper(request) {
@Override
@@ -129,15 +155,34 @@ public class AuthFilterUtils {
return null;
}
- public static void authorizeImpersonationRequest(HttpServletRequest request,
String doAsUser) throws AuthorizationException {
+ public static void authorizeImpersonationRequest(HttpServletRequest request,
String doAsUser, String topologyName, String role) throws
AuthorizationException {
final UserGroupInformation remoteRequestUgi = getRemoteRequestUgi(request,
doAsUser);
if (remoteRequestUgi != null) {
- authorizeImpersonationRequest(request, remoteRequestUgi);
+ authorizeImpersonationRequest(request, remoteRequestUgi, topologyName,
role);
}
}
- private static void authorizeImpersonationRequest(HttpServletRequest
request, UserGroupInformation remoteRequestUgi) throws AuthorizationException {
- ProxyUsers.authorize(remoteRequestUgi, request.getRemoteAddr());
+ private static void authorizeImpersonationRequest(HttpServletRequest
request, UserGroupInformation remoteRequestUgi, String topologyName, String
role)
+ throws AuthorizationException {
+
+ final ImpersonationProvider impersonationProvider =
getImpersonationProvider(topologyName, role);
+
+ if (impersonationProvider != null) {
+ impersonationProvider.authorize(remoteRequestUgi,
request.getRemoteAddr());
+ } else {
+ throw new AuthorizationException("ImpersonationProvider for " +
topologyName + " / " + role + " not found!");
+ }
+ }
+
+ private static ImpersonationProvider getImpersonationProvider(String
topologyName, String role) {
+ refreshSuperUserGroupsLock.lock();
+ final ImpersonationProvider impersonationProvider;
+ try {
+ impersonationProvider =
(TOPOLOGY_IMPERSONATION_PROVIDERS.getOrDefault(topologyName,
Collections.emptyMap())).get(role);
+ } finally {
+ refreshSuperUserGroupsLock.unlock();
+ }
+ return impersonationProvider;
}
private static UserGroupInformation getRemoteRequestUgi(HttpServletRequest
request, String doAsUser) {