KNOX-3048 - Add support for group-based impersonation (#1043)
diff --git a/build.xml b/build.xml
index 23a8772..fc08f22 100644
--- a/build.xml
+++ b/build.xml
@@ -423,6 +423,7 @@
<target name="start-debug-gateway" description="Start test gateway server enabling remote debugging.">
<exec executable="java" dir="${install.dir}/${gateway-artifact}-${gateway-version}">
<arg value="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"/>
+ <arg value="--add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED"/>
<arg value="-jar"/>
<arg value="bin/gateway.jar"/>
</exec>
diff --git a/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/CommonIdentityAssertionFilter.java b/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/CommonIdentityAssertionFilter.java
index f3bb669..f82b8b7 100644
--- a/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/CommonIdentityAssertionFilter.java
+++ b/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/CommonIdentityAssertionFilter.java
@@ -20,6 +20,7 @@
import static org.apache.knox.gateway.identityasserter.common.filter.AbstractIdentityAsserterDeploymentContributor.IMPERSONATION_PARAMS;
import static org.apache.knox.gateway.identityasserter.common.filter.AbstractIdentityAsserterDeploymentContributor.ROLE;
import static org.apache.knox.gateway.identityasserter.common.filter.VirtualGroupMapper.addRequestFunctions;
+import static org.apache.knox.gateway.util.AuthFilterUtils.PROXYGROUP_PREFIX;
import java.io.IOException;
import java.security.AccessController;
@@ -63,285 +64,304 @@
import org.apache.knox.gateway.util.HttpExceptionUtils;
public class CommonIdentityAssertionFilter extends AbstractIdentityAssertionFilter {
- private static final IdentityAsserterMessages LOG = MessagesFactory.get(IdentityAsserterMessages.class);
+ private static final IdentityAsserterMessages LOG = MessagesFactory.get(IdentityAsserterMessages.class);
- public static final String VIRTUAL_GROUP_MAPPING_PREFIX = "group.mapping.";
- public static final String GROUP_PRINCIPAL_MAPPING = "group.principal.mapping";
- public static final String PRINCIPAL_MAPPING = "principal.mapping";
- public static final String ADVANCED_PRINCIPAL_MAPPING = "expression.principal.mapping";
- private static final String PRINCIPAL_PARAM = "user.name";
- private static final String DOAS_PRINCIPAL_PARAM = "doAs";
- static final String IMPERSONATION_ENABLED_PARAM = AuthFilterUtils.PROXYUSER_PREFIX + ".impersonation.enabled";
+ public static final String VIRTUAL_GROUP_MAPPING_PREFIX = "group.mapping.";
+ public static final String GROUP_PRINCIPAL_MAPPING = "group.principal.mapping";
+ public static final String PRINCIPAL_MAPPING = "principal.mapping";
+ public static final String ADVANCED_PRINCIPAL_MAPPING = "expression.principal.mapping";
+ private static final String PRINCIPAL_PARAM = "user.name";
+ private static final String DOAS_PRINCIPAL_PARAM = "doAs";
+ static final String IMPERSONATION_ENABLED_PARAM = AuthFilterUtils.PROXYUSER_PREFIX + ".impersonation.enabled";
- private SimplePrincipalMapper mapper = new SimplePrincipalMapper();
- private final Parser parser = new Parser();
- private VirtualGroupMapper virtualGroupMapper;
- /* List of all default and configured impersonation params */
- protected final List<String> impersonationParamsList = new ArrayList<>();
- protected boolean impersonationEnabled;
- private AbstractSyntaxTree expressionPrincipalMapping;
- private String topologyName;
+ private SimplePrincipalMapper mapper = new SimplePrincipalMapper();
+ private final Parser parser = new Parser();
+ private VirtualGroupMapper virtualGroupMapper;
+ /* List of all default and configured impersonation params */
+ protected final List<String> impersonationParamsList = new ArrayList<>();
+ protected boolean impersonationEnabled;
+ private AbstractSyntaxTree expressionPrincipalMapping;
+ private String topologyName;
+ private boolean hasProxyGroupParams;
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- topologyName = (String) filterConfig.getServletContext().getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE);
- String principalMapping = filterConfig.getInitParameter(PRINCIPAL_MAPPING);
- if (principalMapping == null || principalMapping.isEmpty()) {
- principalMapping = filterConfig.getServletContext().getInitParameter(PRINCIPAL_MAPPING);
- }
- String groupPrincipalMapping = filterConfig.getInitParameter(GROUP_PRINCIPAL_MAPPING);
- if (groupPrincipalMapping == null || groupPrincipalMapping.isEmpty()) {
- groupPrincipalMapping = filterConfig.getServletContext().getInitParameter(GROUP_PRINCIPAL_MAPPING);
- }
- if (principalMapping != null && !principalMapping.isEmpty() || groupPrincipalMapping != null && !groupPrincipalMapping.isEmpty()) {
- try {
- mapper.loadMappingTable(principalMapping, groupPrincipalMapping);
- } catch (PrincipalMappingException e) {
- throw new ServletException("Unable to load principal mapping table.", e);
- }
- }
- expressionPrincipalMapping = parseAdvancedPrincipalMapping(filterConfig);
-
- final List<String> initParameterNames = AuthFilterUtils.getInitParameterNamesAsList(filterConfig);
-
- virtualGroupMapper = new VirtualGroupMapper(loadVirtualGroups(filterConfig, initParameterNames));
-
- initImpersonationParamsList(filterConfig);
- initProxyUserConfiguration(filterConfig, initParameterNames);
- }
-
- private AbstractSyntaxTree parseAdvancedPrincipalMapping(FilterConfig filterConfig) {
- String expression = filterConfig.getInitParameter(ADVANCED_PRINCIPAL_MAPPING);
- if (StringUtils.isBlank(expression)) {
- expression = filterConfig.getServletContext().getInitParameter(ADVANCED_PRINCIPAL_MAPPING);
- }
- return StringUtils.isBlank(expression) ? null : parser.parse(expression);
- }
-
- /*
- * Initialize the impersonation params list.
- * This list contains query params that needs to be scrubbed
- * from the outgoing request.
- */
- private void initImpersonationParamsList(FilterConfig filterConfig) {
- String impersonationListFromConfig = filterConfig.getInitParameter(IMPERSONATION_PARAMS);
- if (impersonationListFromConfig == null || impersonationListFromConfig.isEmpty()) {
- impersonationListFromConfig = filterConfig.getServletContext().getInitParameter(IMPERSONATION_PARAMS);
- }
-
- /* Add default impersonation params */
- impersonationParamsList.add(DOAS_PRINCIPAL_PARAM);
- impersonationParamsList.add(PRINCIPAL_PARAM);
-
- if (impersonationListFromConfig != null && !impersonationListFromConfig.isEmpty()) {
- /* Add configured impersonation params */
- LOG.impersonationConfig(impersonationListFromConfig);
- final StringTokenizer t = new StringTokenizer(impersonationListFromConfig, ",");
- while (t.hasMoreElements()) {
- final String token = t.nextToken().trim();
- if (!impersonationParamsList.contains(token)) {
- impersonationParamsList.add(token);
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ topologyName = (String) filterConfig.getServletContext().getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE);
+ String principalMapping = filterConfig.getInitParameter(PRINCIPAL_MAPPING);
+ if (principalMapping == null || principalMapping.isEmpty()) {
+ principalMapping = filterConfig.getServletContext().getInitParameter(PRINCIPAL_MAPPING);
}
- }
- }
- }
-
- private void initProxyUserConfiguration(FilterConfig filterConfig, List<String> initParameterNames) {
- final String impersonationEnabledValue = filterConfig.getInitParameter(IMPERSONATION_ENABLED_PARAM);
- impersonationEnabled = impersonationEnabledValue == null ? Boolean.FALSE : Boolean.parseBoolean(impersonationEnabledValue);
-
- if (impersonationEnabled) {
- if (AuthFilterUtils.hasProxyConfig(topologyName, "HadoopAuth")) {
- LOG.ignoreProxyuserConfig();
- impersonationEnabled = false; //explicitly set to false to avoid redundant authorization attempts at request processing time
- } else {
- AuthFilterUtils.refreshSuperUserGroupsConfiguration(filterConfig, initParameterNames, topologyName, ROLE);
- filterConfig.getServletContext().setAttribute(ContextAttributes.IMPERSONATION_ENABLED_ATTRIBUTE, Boolean.TRUE);
- }
- } else {
- filterConfig.getServletContext().setAttribute(ContextAttributes.IMPERSONATION_ENABLED_ATTRIBUTE, Boolean.FALSE);
- }
- }
-
- boolean isImpersonationEnabled() {
- return impersonationEnabled;
- }
-
- private Map<String, AbstractSyntaxTree> loadVirtualGroups(FilterConfig filterConfig, List<String> initParameterNames) {
- Map<String, AbstractSyntaxTree> predicateToGroupMapping = new HashMap<>();
- loadVirtualGroupConfig(filterConfig, initParameterNames, predicateToGroupMapping);
- if (predicateToGroupMapping.isEmpty() && filterConfig.getServletContext() != null) {
- loadVirtualGroupConfig(filterConfig.getServletContext(), predicateToGroupMapping);
- }
- return predicateToGroupMapping;
- }
-
- private void loadVirtualGroupConfig(FilterConfig config, List<String> initParameterNames, Map<String, AbstractSyntaxTree> result) {
- for (String paramName : virtualGroupParameterNames(initParameterNames)) {
- addGroup(result, paramName, config.getInitParameter(paramName));
- }
- }
-
- private void loadVirtualGroupConfig(ServletContext context, Map<String, AbstractSyntaxTree> result) {
- final List<String> contextInitParams = context.getInitParameterNames() == null ? Collections.emptyList()
- : Collections.list(context.getInitParameterNames());
- for (String paramName : virtualGroupParameterNames(contextInitParams)) {
- addGroup(result, paramName, context.getInitParameter(paramName));
- }
- }
-
- private void addGroup(Map<String, AbstractSyntaxTree> result, String paramName, String predicate) {
- try {
- AbstractSyntaxTree ast = parser.parse(predicate);
- String groupName = paramName.substring(VIRTUAL_GROUP_MAPPING_PREFIX.length()).trim();
- if (StringUtils.isBlank(groupName)) {
- LOG.missingVirtualGroupName();
- } else {
- result.put(groupName, ast);
- }
- } catch (SyntaxException e) {
- LOG.parseError(paramName, predicate, e);
- }
- }
-
- private static List<String> virtualGroupParameterNames(List<String> initParameterNames) {
- return initParameterNames == null ? new ArrayList<>()
- : initParameterNames.stream().filter(name -> name.startsWith(VIRTUAL_GROUP_MAPPING_PREFIX)).collect(Collectors.toList());
- }
-
- @Override
- public void destroy() {
- }
-
- /**
- * Obtain the standard javax.security.auth.Subject, retrieve the caller principal, map
- * to the identity to be asserted as appropriate and create the provider specific
- * assertion token. Add the assertion token to the request.
- */
- @Override
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- throws IOException, ServletException {
- Subject subject = Subject.getSubject(AccessController.getContext());
-
- if (subject == null) {
- LOG.subjectNotAvailable();
- throw new IllegalStateException("Required Subject Missing");
- }
-
- String mappedPrincipalName = null;
- try {
- mappedPrincipalName = handleProxyUserImpersonation(request, subject);
- } catch(AuthorizationException e) {
- LOG.hadoopAuthProxyUserFailed(e);
- HttpExceptionUtils.createServletExceptionResponse((HttpServletResponse) response, HttpServletResponse.SC_FORBIDDEN, e);
- return;
- }
-
- // mapping principal name using user principal mapping (if configured)
- mappedPrincipalName = mapUserPrincipalBase(mappedPrincipalName);
- mappedPrincipalName = mapUserPrincipal(mappedPrincipalName);
- if (expressionPrincipalMapping != null) {
- String result = evalAdvancedPrincipalMapping(request, subject, mappedPrincipalName);
- if (result != null) {
- mappedPrincipalName = result;
- }
- }
- String[] mappedGroups = mapGroupPrincipalsBase(mappedPrincipalName, subject);
- String[] groups = mapGroupPrincipals(mappedPrincipalName, subject);
- String[] virtualGroups = virtualGroupMapper.mapGroups(mappedPrincipalName, combine(subject, groups), request).toArray(new String[0]);
- groups = combineGroupMappings(mappedGroups, groups);
- groups = combineGroupMappings(virtualGroups, groups);
-
- HttpServletRequestWrapper wrapper = wrapHttpServletRequest(request, mappedPrincipalName);
-
-
- continueChainAsPrincipal(wrapper, response, chain, mappedPrincipalName, unique(groups));
- }
-
- private String evalAdvancedPrincipalMapping(ServletRequest request, Subject subject, String originalPrincipal) {
- Interpreter interpreter = new Interpreter();
- interpreter.addConstant("username", originalPrincipal);
- interpreter.addConstant("groups", groups(subject));
- addRequestFunctions(request, interpreter);
- Object mappedPrincipal = interpreter.eval(expressionPrincipalMapping);
- if (mappedPrincipal instanceof String) {
- return (String)mappedPrincipal;
- } else {
- LOG.invalidAdvancedPrincipalMappingResult(originalPrincipal, expressionPrincipalMapping, mappedPrincipal);
- return null;
- }
- }
-
- private String handleProxyUserImpersonation(ServletRequest request, Subject subject) throws AuthorizationException {
- String principalName = SubjectUtils.getEffectivePrincipalName(subject);
- if (impersonationEnabled) {
- final String doAsUser = request.getParameter(AuthFilterUtils.QUERY_PARAMETER_DOAS);
- if (doAsUser != null && !doAsUser.equals(principalName)) {
- LOG.hadoopAuthDoAsUser(doAsUser, principalName, request.getRemoteAddr());
- if (principalName != null) {
- AuthFilterUtils.authorizeImpersonationRequest((HttpServletRequest) request, principalName, doAsUser, topologyName, ROLE);
- LOG.hadoopAuthProxyUserSuccess();
- principalName = doAsUser;
+ String groupPrincipalMapping = filterConfig.getInitParameter(GROUP_PRINCIPAL_MAPPING);
+ if (groupPrincipalMapping == null || groupPrincipalMapping.isEmpty()) {
+ groupPrincipalMapping = filterConfig.getServletContext().getInitParameter(GROUP_PRINCIPAL_MAPPING);
}
- }
+ if (principalMapping != null && !principalMapping.isEmpty() || groupPrincipalMapping != null && !groupPrincipalMapping.isEmpty()) {
+ try {
+ mapper.loadMappingTable(principalMapping, groupPrincipalMapping);
+ } catch (PrincipalMappingException e) {
+ throw new ServletException("Unable to load principal mapping table.", e);
+ }
+ }
+ expressionPrincipalMapping = parseAdvancedPrincipalMapping(filterConfig);
+
+ final List<String> initParameterNames = AuthFilterUtils.getInitParameterNamesAsList(filterConfig);
+
+ virtualGroupMapper = new VirtualGroupMapper(loadVirtualGroups(filterConfig, initParameterNames));
+
+ initImpersonationParamsList(filterConfig);
+ initProxyUserConfiguration(filterConfig, initParameterNames);
}
- return principalName;
- }
- private Set<String> combine(Subject subject, String[] groups) {
- Set<String> result = groups(subject);
- if (groups != null) {
- result.addAll(Arrays.asList(groups));
+ private AbstractSyntaxTree parseAdvancedPrincipalMapping(FilterConfig filterConfig) {
+ String expression = filterConfig.getInitParameter(ADVANCED_PRINCIPAL_MAPPING);
+ if (StringUtils.isBlank(expression)) {
+ expression = filterConfig.getServletContext().getInitParameter(ADVANCED_PRINCIPAL_MAPPING);
+ }
+ return StringUtils.isBlank(expression) ? null : parser.parse(expression);
}
- return result;
- }
- private static String[] unique(String[] groups) {
- return new HashSet<>(Arrays.asList(groups)).toArray(new String[0]);
- }
+ /*
+ * Initialize the impersonation params list.
+ * This list contains query params that needs to be scrubbed
+ * from the outgoing request.
+ */
+ private void initImpersonationParamsList(FilterConfig filterConfig) {
+ String impersonationListFromConfig = filterConfig.getInitParameter(IMPERSONATION_PARAMS);
+ if (impersonationListFromConfig == null || impersonationListFromConfig.isEmpty()) {
+ impersonationListFromConfig = filterConfig.getServletContext().getInitParameter(IMPERSONATION_PARAMS);
+ }
- protected String[] combineGroupMappings(String[] mappedGroups, String[] groups) {
- if (mappedGroups != null && groups != null) {
- return ArrayUtils.addAll(mappedGroups, groups);
+ /* Add default impersonation params */
+ impersonationParamsList.add(DOAS_PRINCIPAL_PARAM);
+ impersonationParamsList.add(PRINCIPAL_PARAM);
+
+ if (impersonationListFromConfig != null && !impersonationListFromConfig.isEmpty()) {
+ /* Add configured impersonation params */
+ LOG.impersonationConfig(impersonationListFromConfig);
+ final StringTokenizer t = new StringTokenizer(impersonationListFromConfig, ",");
+ while (t.hasMoreElements()) {
+ final String token = t.nextToken().trim();
+ if (!impersonationParamsList.contains(token)) {
+ impersonationParamsList.add(token);
+ }
+ }
+ }
}
- else {
- return groups != null ? groups : mappedGroups;
+
+ private void initProxyUserConfiguration(FilterConfig filterConfig, List<String> initParameterNames) {
+ final String impersonationEnabledValue = filterConfig.getInitParameter(IMPERSONATION_ENABLED_PARAM);
+ impersonationEnabled = impersonationEnabledValue == null ? Boolean.FALSE : Boolean.parseBoolean(impersonationEnabledValue);
+ hasProxyGroupParams = initParameterNames.stream().anyMatch(name -> name.startsWith(PROXYGROUP_PREFIX + "."));
+
+ if (impersonationEnabled) {
+ if (AuthFilterUtils.hasProxyConfig(topologyName, "HadoopAuth")) {
+ LOG.ignoreProxyuserConfig();
+ impersonationEnabled = false; //explicitly set to false to avoid redundant authorization attempts at request processing time
+ } else {
+ AuthFilterUtils.refreshSuperUserGroupsConfiguration(filterConfig, initParameterNames, topologyName, ROLE);
+ filterConfig.getServletContext().setAttribute(ContextAttributes.IMPERSONATION_ENABLED_ATTRIBUTE, Boolean.TRUE);
+ }
+ } else {
+ filterConfig.getServletContext().setAttribute(ContextAttributes.IMPERSONATION_ENABLED_ATTRIBUTE, Boolean.FALSE);
+ }
}
- }
- public HttpServletRequestWrapper wrapHttpServletRequest(
- ServletRequest request, String mappedPrincipalName) {
- // wrap the request so that the proper principal is returned
- // from request methods
- return new IdentityAsserterHttpServletRequestWrapper(
- (HttpServletRequest) request,
- mappedPrincipalName,
- impersonationParamsList);
- }
+ boolean isImpersonationEnabled() {
+ return impersonationEnabled;
+ }
- protected String[] mapGroupPrincipalsBase(String mappedPrincipalName, Subject subject) {
- return mapper.mapGroupPrincipal(mappedPrincipalName);
- }
+ private Map<String, AbstractSyntaxTree> loadVirtualGroups(FilterConfig filterConfig, List<String> initParameterNames) {
+ Map<String, AbstractSyntaxTree> predicateToGroupMapping = new HashMap<>();
+ loadVirtualGroupConfig(filterConfig, initParameterNames, predicateToGroupMapping);
+ if (predicateToGroupMapping.isEmpty() && filterConfig.getServletContext() != null) {
+ loadVirtualGroupConfig(filterConfig.getServletContext(), predicateToGroupMapping);
+ }
+ return predicateToGroupMapping;
+ }
- protected String mapUserPrincipalBase(String principalName) {
- return mapper.mapUserPrincipal(principalName);
- }
+ private void loadVirtualGroupConfig(FilterConfig config, List<String> initParameterNames, Map<String, AbstractSyntaxTree> result) {
+ for (String paramName : virtualGroupParameterNames(initParameterNames)) {
+ addGroup(result, paramName, config.getInitParameter(paramName));
+ }
+ }
- private Set<String> groups(Subject subject) {
- return subject.getPrincipals(GroupPrincipal.class).stream()
- .map(GroupPrincipal::getName)
- .collect(Collectors.toSet());
- }
+ private void loadVirtualGroupConfig(ServletContext context, Map<String, AbstractSyntaxTree> result) {
+ final List<String> contextInitParams = context.getInitParameterNames() == null ? Collections.emptyList()
+ : Collections.list(context.getInitParameterNames());
+ for (String paramName : virtualGroupParameterNames(contextInitParams)) {
+ addGroup(result, paramName, context.getInitParameter(paramName));
+ }
+ }
- @Override
- public String[] mapGroupPrincipals(String mappedPrincipalName, Subject subject) {
- // NOP
- return null;
- }
+ private void addGroup(Map<String, AbstractSyntaxTree> result, String paramName, String predicate) {
+ try {
+ AbstractSyntaxTree ast = parser.parse(predicate);
+ String groupName = paramName.substring(VIRTUAL_GROUP_MAPPING_PREFIX.length()).trim();
+ if (StringUtils.isBlank(groupName)) {
+ LOG.missingVirtualGroupName();
+ } else {
+ result.put(groupName, ast);
+ }
+ } catch (SyntaxException e) {
+ LOG.parseError(paramName, predicate, e);
+ }
+ }
- @Override
- public String mapUserPrincipal(String principalName) {
- // NOP
- return principalName;
- }
+ private static List<String> virtualGroupParameterNames(List<String> initParameterNames) {
+ return initParameterNames == null ? new ArrayList<>()
+ : initParameterNames.stream().filter(name -> name.startsWith(VIRTUAL_GROUP_MAPPING_PREFIX)).collect(Collectors.toList());
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ /**
+ * Obtain the standard javax.security.auth.Subject, retrieve the caller principal, map
+ * to the identity to be asserted as appropriate and create the provider specific
+ * assertion token. Add the assertion token to the request.
+ */
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ Subject subject = Subject.getSubject(AccessController.getContext());
+
+ if (subject == null) {
+ LOG.subjectNotAvailable();
+ throw new IllegalStateException("Required Subject Missing");
+ }
+
+ String principalName = SubjectUtils.getEffectivePrincipalName(subject);
+ String[] groupsForPrincipal = {};
+ /* get groups from non-impersonated user IF groups params are configured */
+ if(hasProxyGroupParams) {
+ groupsForPrincipal = getGroupsForPrincipal(principalName, subject, request);
+ }
+
+ String mappedPrincipalName = null;
+ try {
+ mappedPrincipalName = handleProxyUserImpersonation(request, subject, groupsForPrincipal);
+ } catch(AuthorizationException e) {
+ LOG.hadoopAuthProxyUserFailed(e);
+ HttpExceptionUtils.createServletExceptionResponse((HttpServletResponse) response, HttpServletResponse.SC_FORBIDDEN, e);
+ return;
+ }
+
+
+ // mapping principal name using user principal mapping (if configured)
+ mappedPrincipalName = mapUserPrincipalBase(mappedPrincipalName);
+ mappedPrincipalName = mapUserPrincipal(mappedPrincipalName);
+ if (expressionPrincipalMapping != null) {
+ String result = evalAdvancedPrincipalMapping(request, subject, mappedPrincipalName);
+ if (result != null) {
+ mappedPrincipalName = result;
+ }
+ }
+
+ String[] groups = getGroupsForPrincipal(mappedPrincipalName, subject, request);
+
+
+
+
+ HttpServletRequestWrapper wrapper = wrapHttpServletRequest(request, mappedPrincipalName);
+
+
+ continueChainAsPrincipal(wrapper, response, chain, mappedPrincipalName, unique(groups));
+ }
+
+ private String[] getGroupsForPrincipal(String mappedPrincipalName, Subject subject, ServletRequest request) {
+ String[] mappedGroups = mapGroupPrincipalsBase(mappedPrincipalName, subject);
+ String[] groups = mapGroupPrincipals(mappedPrincipalName, subject);
+ String[] virtualGroups = virtualGroupMapper.mapGroups(mappedPrincipalName, combine(subject, groups), request).toArray(new String[0]);
+ groups = combineGroupMappings(mappedGroups, groups);
+ groups = combineGroupMappings(virtualGroups, groups);
+ return groups;
+ }
+
+ private String evalAdvancedPrincipalMapping(ServletRequest request, Subject subject, String originalPrincipal) {
+ Interpreter interpreter = new Interpreter();
+ interpreter.addConstant("username", originalPrincipal);
+ interpreter.addConstant("groups", groups(subject));
+ addRequestFunctions(request, interpreter);
+ Object mappedPrincipal = interpreter.eval(expressionPrincipalMapping);
+ if (mappedPrincipal instanceof String) {
+ return (String)mappedPrincipal;
+ } else {
+ LOG.invalidAdvancedPrincipalMappingResult(originalPrincipal, expressionPrincipalMapping, mappedPrincipal);
+ return null;
+ }
+ }
+
+ private String handleProxyUserImpersonation(ServletRequest request, Subject subject, String[] groups) throws AuthorizationException {
+ String principalName = SubjectUtils.getEffectivePrincipalName(subject);
+ if (impersonationEnabled) {
+ final String doAsUser = request.getParameter(AuthFilterUtils.QUERY_PARAMETER_DOAS);
+ if (doAsUser != null && !doAsUser.equals(principalName)) {
+ LOG.hadoopAuthDoAsUser(doAsUser, principalName, request.getRemoteAddr());
+ if (principalName != null) {
+ AuthFilterUtils.authorizeImpersonationRequest((HttpServletRequest) request, principalName, doAsUser, topologyName, ROLE, Arrays.asList(groups));
+ LOG.hadoopAuthProxyUserSuccess();
+ principalName = doAsUser;
+ }
+ }
+ }
+ return principalName;
+ }
+
+ private Set<String> combine(Subject subject, String[] groups) {
+ Set<String> result = groups(subject);
+ if (groups != null) {
+ result.addAll(Arrays.asList(groups));
+ }
+ return result;
+ }
+
+ private static String[] unique(String[] groups) {
+ return new HashSet<>(Arrays.asList(groups)).toArray(new String[0]);
+ }
+
+ protected String[] combineGroupMappings(String[] mappedGroups, String[] groups) {
+ if (mappedGroups != null && groups != null) {
+ return ArrayUtils.addAll(mappedGroups, groups);
+ }
+ else {
+ return groups != null ? groups : mappedGroups;
+ }
+ }
+
+ public HttpServletRequestWrapper wrapHttpServletRequest(
+ ServletRequest request, String mappedPrincipalName) {
+ // wrap the request so that the proper principal is returned
+ // from request methods
+ return new IdentityAsserterHttpServletRequestWrapper(
+ (HttpServletRequest) request,
+ mappedPrincipalName,
+ impersonationParamsList);
+ }
+
+ protected String[] mapGroupPrincipalsBase(String mappedPrincipalName, Subject subject) {
+ return mapper.mapGroupPrincipal(mappedPrincipalName);
+ }
+
+ protected String mapUserPrincipalBase(String principalName) {
+ return mapper.mapUserPrincipal(principalName);
+ }
+
+ private Set<String> groups(Subject subject) {
+ return subject.getPrincipals(GroupPrincipal.class).stream()
+ .map(GroupPrincipal::getName)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public String[] mapGroupPrincipals(String mappedPrincipalName, Subject subject) {
+ // NOP
+ return null;
+ }
+
+ @Override
+ public String mapUserPrincipal(String principalName) {
+ // NOP
+ return principalName;
+ }
}
diff --git a/gateway-provider-identity-assertion-common/src/test/java/org/apache/knox/gateway/identityasserter/common/filter/CommonIdentityAssertionFilterTest.java b/gateway-provider-identity-assertion-common/src/test/java/org/apache/knox/gateway/identityasserter/common/filter/CommonIdentityAssertionFilterTest.java
index 4d3f9ec..942065b 100644
--- a/gateway-provider-identity-assertion-common/src/test/java/org/apache/knox/gateway/identityasserter/common/filter/CommonIdentityAssertionFilterTest.java
+++ b/gateway-provider-identity-assertion-common/src/test/java/org/apache/knox/gateway/identityasserter/common/filter/CommonIdentityAssertionFilterTest.java
@@ -61,247 +61,247 @@
import org.junit.Test;
public class CommonIdentityAssertionFilterTest {
- private String username;
- private CommonIdentityAssertionFilter filter;
- private Set<String> calculatedGroups = new HashSet<>();
+ private String username;
+ private CommonIdentityAssertionFilter filter;
+ private Set<String> calculatedGroups = new HashSet<>();
- @Before
- public void setUp() {
- filter = new CommonIdentityAssertionFilter() {
- @Override
- public String mapUserPrincipal(String principalName) {
- username = principalName.toUpperCase(Locale.ROOT);
- return principalName;
- }
+ @Before
+ public void setUp() {
+ filter = new CommonIdentityAssertionFilter() {
+ @Override
+ public String mapUserPrincipal(String principalName) {
+ username = principalName.toUpperCase(Locale.ROOT);
+ return principalName;
+ }
- @Override
- public String[] mapGroupPrincipals(String principalName, Subject subject) {
- String[] groups = new String[4];
- int i = 0;
- for(GroupPrincipal p : subject.getPrincipals(GroupPrincipal.class)) {
- groups[i] = p.getName().toUpperCase(Locale.ROOT);
- i++;
+ @Override
+ public String[] mapGroupPrincipals(String principalName, Subject subject) {
+ String[] groups = new String[4];
+ int i = 0;
+ for(GroupPrincipal p : subject.getPrincipals(GroupPrincipal.class)) {
+ groups[i] = p.getName().toUpperCase(Locale.ROOT);
+ i++;
+ }
+ return groups;
+ }
+
+ @Override
+ protected String[] combineGroupMappings(String[] mappedGroups, String[] groups) {
+ calculatedGroups.addAll(Arrays.asList(super.combineGroupMappings(mappedGroups, groups)));
+ return super.combineGroupMappings(mappedGroups, groups);
+ }
+
+ @Override
+ protected void continueChainAsPrincipal(HttpServletRequestWrapper request, ServletResponse response, FilterChain chain, String mappedPrincipalName, String[] groups) throws IOException, ServletException {
+ assertEquals("Groups should not have duplicates: " + Arrays.toString(groups),
+ new HashSet<>(Arrays.asList(groups)).size(),
+ groups.length);
+ super.continueChainAsPrincipal(request, response, chain, mappedPrincipalName, groups);
+ }
+ };
+ ThreadContext.put(MDC_AUDIT_CONTEXT_KEY, "dummy");
+ }
+
+ @Test
+ public void testSimpleFilter() throws ServletException, IOException {
+ ServletContext servletContext = createMock(ServletContext.class);
+ EasyMock.expect(servletContext.getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE)).andReturn("topology1").anyTimes();
+ servletContext.setAttribute(ContextAttributes.IMPERSONATION_ENABLED_ATTRIBUTE, Boolean.FALSE);
+ EasyMock.expectLastCall();
+ EasyMock.replay(servletContext);
+ FilterConfig config = EasyMock.createNiceMock( FilterConfig.class );
+ EasyMock.expect(config.getServletContext()).andReturn(servletContext).anyTimes();
+ EasyMock.expect(config.getInitParameter(CommonIdentityAssertionFilter.ADVANCED_PRINCIPAL_MAPPING)).
+ andReturn("username").anyTimes();
+ EasyMock.expect(config.getInitParameter(CommonIdentityAssertionFilter.GROUP_PRINCIPAL_MAPPING)).
+ andReturn("*=everyone;lmccay=test-virtual-group").once();
+ EasyMock.expect(config.getInitParameter(CommonIdentityAssertionFilter.PRINCIPAL_MAPPING)).
+ andReturn("ljm=lmccay;").once();
+ EasyMock.expect(config.getInitParameterNames()).
+ andReturn(Collections.enumeration(Arrays.asList(
+ CommonIdentityAssertionFilter.GROUP_PRINCIPAL_MAPPING,
+ CommonIdentityAssertionFilter.PRINCIPAL_MAPPING,
+ CommonIdentityAssertionFilter.VIRTUAL_GROUP_MAPPING_PREFIX + "test-virtual-group",
+ CommonIdentityAssertionFilter.VIRTUAL_GROUP_MAPPING_PREFIX))) // invalid group with no name
+ .anyTimes();
+ EasyMock.expect(config.getInitParameter(IMPERSONATION_PARAMS)).
+ andReturn("doAs").anyTimes();
+ EasyMock.expect(config.getInitParameter(CommonIdentityAssertionFilter.VIRTUAL_GROUP_MAPPING_PREFIX + "test-virtual-group")).
+ andReturn("(and (username 'lmccay') (and (member 'users') (member 'admin')))").anyTimes();
+ EasyMock.expect(config.getInitParameter(CommonIdentityAssertionFilter.VIRTUAL_GROUP_MAPPING_PREFIX)).
+ andReturn("true").anyTimes();
+ EasyMock.replay( config );
+
+ final HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class );
+ EasyMock.replay( request );
+
+ final HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class );
+ EasyMock.replay( response );
+
+ final FilterChain chain = (req, resp) -> {};
+
+ Subject subject = new Subject();
+ subject.getPrincipals().add(new PrimaryPrincipal("ljm"));
+ subject.getPrincipals().add(new GroupPrincipal("users"));
+ subject.getPrincipals().add(new GroupPrincipal("admin"));
+ try {
+ Subject.doAs(
+ subject,
+ (PrivilegedExceptionAction<Object>) () -> {
+ filter.init(config);
+ filter.doFilter(request, response, chain);
+ return null;
+ });
}
- return groups;
- }
+ catch (PrivilegedActionException e) {
+ Throwable t = e.getCause();
+ if (t instanceof IOException) {
+ throw (IOException) t;
+ }
+ else if (t instanceof ServletException) {
+ throw (ServletException) t;
+ }
+ else {
+ throw new ServletException(t);
+ }
+ }
- @Override
- protected String[] combineGroupMappings(String[] mappedGroups, String[] groups) {
- calculatedGroups.addAll(Arrays.asList(super.combineGroupMappings(mappedGroups, groups)));
- return super.combineGroupMappings(mappedGroups, groups);
- }
-
- @Override
- protected void continueChainAsPrincipal(HttpServletRequestWrapper request, ServletResponse response, FilterChain chain, String mappedPrincipalName, String[] groups) throws IOException, ServletException {
- assertEquals("Groups should not have duplicates: " + Arrays.toString(groups),
- new HashSet<>(Arrays.asList(groups)).size(),
- groups.length);
- super.continueChainAsPrincipal(request, response, chain, mappedPrincipalName, groups);
- }
- };
- ThreadContext.put(MDC_AUDIT_CONTEXT_KEY, "dummy");
- }
-
- @Test
- public void testSimpleFilter() throws ServletException, IOException {
- ServletContext servletContext = createMock(ServletContext.class);
- EasyMock.expect(servletContext.getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE)).andReturn("topology1").anyTimes();
- servletContext.setAttribute(ContextAttributes.IMPERSONATION_ENABLED_ATTRIBUTE, Boolean.FALSE);
- EasyMock.expectLastCall();
- EasyMock.replay(servletContext);
- FilterConfig config = EasyMock.createNiceMock( FilterConfig.class );
- EasyMock.expect(config.getServletContext()).andReturn(servletContext).anyTimes();
- EasyMock.expect(config.getInitParameter(CommonIdentityAssertionFilter.ADVANCED_PRINCIPAL_MAPPING)).
- andReturn("username").anyTimes();
- EasyMock.expect(config.getInitParameter(CommonIdentityAssertionFilter.GROUP_PRINCIPAL_MAPPING)).
- andReturn("*=everyone;lmccay=test-virtual-group").once();
- EasyMock.expect(config.getInitParameter(CommonIdentityAssertionFilter.PRINCIPAL_MAPPING)).
- andReturn("ljm=lmccay;").once();
- EasyMock.expect(config.getInitParameterNames()).
- andReturn(Collections.enumeration(Arrays.asList(
- CommonIdentityAssertionFilter.GROUP_PRINCIPAL_MAPPING,
- CommonIdentityAssertionFilter.PRINCIPAL_MAPPING,
- CommonIdentityAssertionFilter.VIRTUAL_GROUP_MAPPING_PREFIX + "test-virtual-group",
- CommonIdentityAssertionFilter.VIRTUAL_GROUP_MAPPING_PREFIX))) // invalid group with no name
- .anyTimes();
- EasyMock.expect(config.getInitParameter(IMPERSONATION_PARAMS)).
- andReturn("doAs").anyTimes();
- EasyMock.expect(config.getInitParameter(CommonIdentityAssertionFilter.VIRTUAL_GROUP_MAPPING_PREFIX + "test-virtual-group")).
- andReturn("(and (username 'lmccay') (and (member 'users') (member 'admin')))").anyTimes();
- EasyMock.expect(config.getInitParameter(CommonIdentityAssertionFilter.VIRTUAL_GROUP_MAPPING_PREFIX)).
- andReturn("true").anyTimes();
- EasyMock.replay( config );
-
- final HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class );
- EasyMock.replay( request );
-
- final HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class );
- EasyMock.replay( response );
-
- final FilterChain chain = (req, resp) -> {};
-
- Subject subject = new Subject();
- subject.getPrincipals().add(new PrimaryPrincipal("ljm"));
- subject.getPrincipals().add(new GroupPrincipal("users"));
- subject.getPrincipals().add(new GroupPrincipal("admin"));
- try {
- Subject.doAs(
- subject,
- (PrivilegedExceptionAction<Object>) () -> {
- filter.init(config);
- filter.doFilter(request, response, chain);
- return null;
- });
- }
- catch (PrivilegedActionException e) {
- Throwable t = e.getCause();
- if (t instanceof IOException) {
- throw (IOException) t;
- }
- else if (t instanceof ServletException) {
- throw (ServletException) t;
- }
- else {
- throw new ServletException(t);
- }
+ assertEquals("LMCCAY", username);
+ assertTrue("Should be greater than 2", calculatedGroups.size() > 2);
+ assertTrue(calculatedGroups.containsAll(Arrays.asList("everyone", "USERS", "ADMIN", "test-virtual-group")));
+ assertFalse(calculatedGroups.contains(""));
}
- assertEquals("LMCCAY", username);
- assertTrue("Should be greater than 2", calculatedGroups.size() > 2);
- assertTrue(calculatedGroups.containsAll(Arrays.asList("everyone", "USERS", "ADMIN", "test-virtual-group")));
- assertFalse(calculatedGroups.contains(""));
- }
+ @Test
+ public void testProxyUserImpersonationDisabled() throws Exception {
+ testProxyUserImpersonationEnabled(false);
- @Test
- public void testProxyUserImpersonationDisabled() throws Exception {
- testProxyUserImpersonationEnabled(false);
-
- }
-
- @Test
- public void testProxyUserImpersonationEnabled() throws Exception {
- testProxyUserImpersonationEnabled(true);
- }
-
- @Test
- public void testProxyUserImpersonationEnabledAndConfiguredInHadoopAuth() throws Exception {
- testProxyUserImpersonationEnabled(true, true, Arrays.asList());
- }
-
- @Test
- public void testProxyUserImpersonationFailWithoutProxyUserConfig() throws Exception {
- final String impersonatedUser = "bob";
-
- // enable impersonation without configuring context/filter expectations
- testProxyUserImpersonationEnabled(true);
-
- final HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
- EasyMock.expect(request.getParameter(AuthFilterUtils.QUERY_PARAMETER_DOAS)).andReturn(impersonatedUser).once();
-
- final ByteArrayOutputStream out = new ByteArrayOutputStream();
- final PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
- final HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
- EasyMock.expect(response.getWriter()).andReturn(writer).anyTimes();
- EasyMock.replay(request, response);
-
- final FilterChainWrapper chain = new FilterChainWrapper((req, resp) -> {
- });
-
- final Subject subject = new Subject();
- subject.getPrincipals().add(new PrimaryPrincipal("admin"));
- Subject.doAs(subject, (PrivilegedExceptionAction<Object>) () -> {
- filter.doFilter(request, response, chain);
- return null;
- });
- assertTrue(new String(out.toByteArray(), StandardCharsets.UTF_8).contains("User: admin is not allowed to impersonate bob"));
- }
-
- @Test
- public void testProxyUserImpersonationApplied() throws Exception {
- final String impersonatedUser = "bob";
-
- // enable impersonation and configure context/filter expectations
- final List<String> proxyUserConfig = Arrays.asList(AuthFilterUtils.PROXYUSER_PREFIX + ".admin.users", AuthFilterUtils.PROXYUSER_PREFIX + ".admin.hosts");
- testProxyUserImpersonationEnabled(true, false, proxyUserConfig);
-
- final HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
- EasyMock.expect(request.getParameter(AuthFilterUtils.QUERY_PARAMETER_DOAS)).andReturn(impersonatedUser).once();
- final HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
- EasyMock.replay(request, response);
-
- final FilterChainWrapper chain = new FilterChainWrapper((req, resp) -> {
- });
-
- final Subject subject = new Subject();
- subject.getPrincipals().add(new PrimaryPrincipal("admin"));
- Subject.doAs(subject, (PrivilegedExceptionAction<Object>) () -> {
- filter.doFilter(request, response, chain);
- return null;
- });
- assertTrue(filter.isImpersonationEnabled());
- final Subject impersonatedSubject = chain.getSubject();
- assertTrue(SubjectUtils.isImpersonating(impersonatedSubject));
- assertEquals("admin", SubjectUtils.getPrimaryPrincipalName(impersonatedSubject));
- assertEquals("bob", SubjectUtils.getEffectivePrincipalName(impersonatedSubject));
- }
-
- private static final class FilterChainWrapper implements FilterChain {
- private final FilterChain wrappedFilterChain;
- private Subject subject;
-
- FilterChainWrapper(FilterChain wrappedFilterChain) {
- this.wrappedFilterChain = wrappedFilterChain;
}
- @Override
- public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
- this.subject = SubjectUtils.getCurrentSubject();
- wrappedFilterChain.doFilter(request, response);
+ @Test
+ public void testProxyUserImpersonationEnabled() throws Exception {
+ testProxyUserImpersonationEnabled(true);
}
- Subject getSubject() {
- return subject;
- }
- }
-
- private void testProxyUserImpersonationEnabled(boolean impersonationEnabled) throws Exception {
- testProxyUserImpersonationEnabled(impersonationEnabled, false, Arrays.asList());
- }
-
- private void testProxyUserImpersonationEnabled(boolean impersonationEnabled, boolean configuredInHadoopAuth, List<String> filterConfigParameterNames)
- throws Exception {
- ServletContext servletContext = createMock(ServletContext.class);
- EasyMock.expect(servletContext.getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE)).andReturn("topology1").anyTimes();
- EasyMock.expect(servletContext.getInitParameter(CommonIdentityAssertionFilter.PRINCIPAL_MAPPING)).andReturn(null).anyTimes();
- EasyMock.expect(servletContext.getInitParameter(CommonIdentityAssertionFilter.GROUP_PRINCIPAL_MAPPING)).andReturn(null).anyTimes();
- EasyMock.expect(servletContext.getInitParameter(CommonIdentityAssertionFilter.ADVANCED_PRINCIPAL_MAPPING)).andReturn("username").anyTimes();
- EasyMock.expect(servletContext.getInitParameterNames()).andReturn(Collections.enumeration(filterConfigParameterNames)).anyTimes();
- EasyMock.expect(servletContext.getInitParameter(IMPERSONATION_PARAMS)).andReturn("doAs").anyTimes();
- if (!configuredInHadoopAuth) {
- servletContext.setAttribute(ContextAttributes.IMPERSONATION_ENABLED_ATTRIBUTE, Boolean.valueOf(impersonationEnabled));
- EasyMock.expectLastCall();
- }
- final FilterConfig filterConfig = EasyMock.createNiceMock(FilterConfig.class);
- final String impersonatedEnabledParam = impersonationEnabled ? Boolean.TRUE.toString() : Boolean.FALSE.toString();
- EasyMock.expect(filterConfig.getInitParameter(CommonIdentityAssertionFilter.IMPERSONATION_ENABLED_PARAM)).andReturn(impersonatedEnabledParam);
- EasyMock.expect(filterConfig.getInitParameter(CommonIdentityAssertionFilter.PRINCIPAL_MAPPING)).andReturn(null).anyTimes();
- EasyMock.expect(filterConfig.getInitParameter(CommonIdentityAssertionFilter.GROUP_PRINCIPAL_MAPPING)).andReturn(null).anyTimes();
- EasyMock.expect(filterConfig.getInitParameterNames()).andReturn(Collections.enumeration(filterConfigParameterNames)).atLeastOnce();
- filterConfigParameterNames.forEach(filterConfigParameterName -> {
- EasyMock.expect(filterConfig.getInitParameter(filterConfigParameterName)).andReturn("*").anyTimes();
- });
- EasyMock.expect(filterConfig.getServletContext()).andReturn(servletContext).anyTimes();
- EasyMock.replay(servletContext, filterConfig);
-
- if (configuredInHadoopAuth) {
- AuthFilterUtils.refreshSuperUserGroupsConfiguration(filterConfig, Arrays.asList(), "topology1", "HadoopAuth");
- } else {
- AuthFilterUtils.removeProxyUserConfig("topology1", "HadoopAuth");
+ @Test
+ public void testProxyUserImpersonationEnabledAndConfiguredInHadoopAuth() throws Exception {
+ testProxyUserImpersonationEnabled(true, true, Arrays.asList());
}
- filter.init(filterConfig);
- assertEquals(impersonationEnabled && !configuredInHadoopAuth, filter.isImpersonationEnabled());
- EasyMock.verify(servletContext, filterConfig);
- }
+ @Test
+ public void testProxyUserImpersonationFailWithoutProxyUserConfig() throws Exception {
+ final String impersonatedUser = "bob";
+
+ // enable impersonation without configuring context/filter expectations
+ testProxyUserImpersonationEnabled(true);
+
+ final HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ EasyMock.expect(request.getParameter(AuthFilterUtils.QUERY_PARAMETER_DOAS)).andReturn(impersonatedUser).once();
+
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ final PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
+ final HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.getWriter()).andReturn(writer).anyTimes();
+ EasyMock.replay(request, response);
+
+ final FilterChainWrapper chain = new FilterChainWrapper((req, resp) -> {
+ });
+
+ final Subject subject = new Subject();
+ subject.getPrincipals().add(new PrimaryPrincipal("admin"));
+ Subject.doAs(subject, (PrivilegedExceptionAction<Object>) () -> {
+ filter.doFilter(request, response, chain);
+ return null;
+ });
+ assertTrue(new String(out.toByteArray(), StandardCharsets.UTF_8).contains("User: admin (auth:SIMPLE) is not allowed to impersonate bob"));
+ }
+
+ @Test
+ public void testProxyUserImpersonationApplied() throws Exception {
+ final String impersonatedUser = "bob";
+
+ // enable impersonation and configure context/filter expectations
+ final List<String> proxyUserConfig = Arrays.asList(AuthFilterUtils.PROXYUSER_PREFIX + ".admin.users", AuthFilterUtils.PROXYUSER_PREFIX + ".admin.hosts");
+ testProxyUserImpersonationEnabled(true, false, proxyUserConfig);
+
+ final HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ EasyMock.expect(request.getParameter(AuthFilterUtils.QUERY_PARAMETER_DOAS)).andReturn(impersonatedUser).once();
+ final HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.replay(request, response);
+
+ final FilterChainWrapper chain = new FilterChainWrapper((req, resp) -> {
+ });
+
+ final Subject subject = new Subject();
+ subject.getPrincipals().add(new PrimaryPrincipal("admin"));
+ Subject.doAs(subject, (PrivilegedExceptionAction<Object>) () -> {
+ filter.doFilter(request, response, chain);
+ return null;
+ });
+ assertTrue(filter.isImpersonationEnabled());
+ final Subject impersonatedSubject = chain.getSubject();
+ assertTrue(SubjectUtils.isImpersonating(impersonatedSubject));
+ assertEquals("admin", SubjectUtils.getPrimaryPrincipalName(impersonatedSubject));
+ assertEquals("bob", SubjectUtils.getEffectivePrincipalName(impersonatedSubject));
+ }
+
+ private static final class FilterChainWrapper implements FilterChain {
+ private final FilterChain wrappedFilterChain;
+ private Subject subject;
+
+ FilterChainWrapper(FilterChain wrappedFilterChain) {
+ this.wrappedFilterChain = wrappedFilterChain;
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
+ this.subject = SubjectUtils.getCurrentSubject();
+ wrappedFilterChain.doFilter(request, response);
+ }
+
+ Subject getSubject() {
+ return subject;
+ }
+ }
+
+ private void testProxyUserImpersonationEnabled(boolean impersonationEnabled) throws Exception {
+ testProxyUserImpersonationEnabled(impersonationEnabled, false, Arrays.asList());
+ }
+
+ private void testProxyUserImpersonationEnabled(boolean impersonationEnabled, boolean configuredInHadoopAuth, List<String> filterConfigParameterNames)
+ throws Exception {
+ ServletContext servletContext = createMock(ServletContext.class);
+ EasyMock.expect(servletContext.getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE)).andReturn("topology1").anyTimes();
+ EasyMock.expect(servletContext.getInitParameter(CommonIdentityAssertionFilter.PRINCIPAL_MAPPING)).andReturn(null).anyTimes();
+ EasyMock.expect(servletContext.getInitParameter(CommonIdentityAssertionFilter.GROUP_PRINCIPAL_MAPPING)).andReturn(null).anyTimes();
+ EasyMock.expect(servletContext.getInitParameter(CommonIdentityAssertionFilter.ADVANCED_PRINCIPAL_MAPPING)).andReturn("username").anyTimes();
+ EasyMock.expect(servletContext.getInitParameterNames()).andReturn(Collections.enumeration(filterConfigParameterNames)).anyTimes();
+ EasyMock.expect(servletContext.getInitParameter(IMPERSONATION_PARAMS)).andReturn("doAs").anyTimes();
+ if (!configuredInHadoopAuth) {
+ servletContext.setAttribute(ContextAttributes.IMPERSONATION_ENABLED_ATTRIBUTE, Boolean.valueOf(impersonationEnabled));
+ EasyMock.expectLastCall();
+ }
+ final FilterConfig filterConfig = EasyMock.createNiceMock(FilterConfig.class);
+ final String impersonatedEnabledParam = impersonationEnabled ? Boolean.TRUE.toString() : Boolean.FALSE.toString();
+ EasyMock.expect(filterConfig.getInitParameter(CommonIdentityAssertionFilter.IMPERSONATION_ENABLED_PARAM)).andReturn(impersonatedEnabledParam);
+ EasyMock.expect(filterConfig.getInitParameter(CommonIdentityAssertionFilter.PRINCIPAL_MAPPING)).andReturn(null).anyTimes();
+ EasyMock.expect(filterConfig.getInitParameter(CommonIdentityAssertionFilter.GROUP_PRINCIPAL_MAPPING)).andReturn(null).anyTimes();
+ EasyMock.expect(filterConfig.getInitParameterNames()).andReturn(Collections.enumeration(filterConfigParameterNames)).atLeastOnce();
+ filterConfigParameterNames.forEach(filterConfigParameterName -> {
+ EasyMock.expect(filterConfig.getInitParameter(filterConfigParameterName)).andReturn("*").anyTimes();
+ });
+ EasyMock.expect(filterConfig.getServletContext()).andReturn(servletContext).anyTimes();
+ EasyMock.replay(servletContext, filterConfig);
+
+ if (configuredInHadoopAuth) {
+ AuthFilterUtils.refreshSuperUserGroupsConfiguration(filterConfig, Arrays.asList(), "topology1", "HadoopAuth");
+ } else {
+ AuthFilterUtils.removeProxyUserConfig("topology1", "HadoopAuth");
+ }
+
+ filter.init(filterConfig);
+ assertEquals(impersonationEnabled && !configuredInHadoopAuth, filter.isImpersonationEnabled());
+ EasyMock.verify(servletContext, filterConfig);
+ }
}
diff --git a/gateway-spi/pom.xml b/gateway-spi/pom.xml
index d09c39b..7796e8a 100644
--- a/gateway-spi/pom.xml
+++ b/gateway-spi/pom.xml
@@ -216,5 +216,42 @@
<artifactId>velocity-engine-core</artifactId>
<scope>test</scope>
</dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-auth</artifactId>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>javax.activation</groupId>
+ <artifactId>javax.activation-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>dnsjava</groupId>
+ <artifactId>dnsjava</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-annotations</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>javax.activation</groupId>
+ <artifactId>javax.activation-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>dnsjava</groupId>
+ <artifactId>dnsjava</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
</dependencies>
</project>
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 e055922..7217499 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
@@ -85,6 +85,9 @@
@Message(level = MessageLevel.ERROR, text = "Failed to load truststore due to {0}")
void failedToLoadTruststore(String message, @StackTrace(level = MessageLevel.DEBUG) Exception e);
+ @Message(level = MessageLevel.ERROR, text = "Impersonation failed, user {0} does not belong to configured proxy group")
+ void failedToImpersonateViaGroups(String user);
+
@Message(level = MessageLevel.WARN, text = "Duplicated filter param key: {0}")
void duplicatedFilterParamKey(String name);
@@ -93,4 +96,25 @@
@Message(level=MessageLevel.DEBUG, text="Ignoring cookie path scope filter for default topology")
void ignoringCookiePathScopeForDefaultTopology();
+
+ @Message(level=MessageLevel.DEBUG, text="Loaded proxy groups ACLs: {0}")
+ void loadedProxyGroupsAcls(String acls);
+
+ @Message(level = MessageLevel.ERROR, text = "User impersonation failed for user {0}. Connections from remote address {1} are not authorized.")
+ void failedToImpersonateUserFromIP(String user, String address);
+
+ @Message(level = MessageLevel.ERROR, text = "User impersonation failed for user {0}, cause {1}. Trying group impersonation ...")
+ void failedToImpersonateUserTryingGroups(String user, String cause);
+
+ @Message(level = MessageLevel.INFO, text = "User {0} is allowed to impersonate user {1}")
+ void successfulImpersonation(String user, String iuser);
+
+ @Message(level = MessageLevel.ERROR, text = "User {0} with groups {1} is not allowed to impersonate {2}")
+ void failedToImpersonateGroups(String user, String groups, String iuser);
+
+ @Message(level = MessageLevel.ERROR, text = "User {0} with groups {1} is not allowed to impersonate {2} from address {3}")
+ void failedToImpersonateGroupsFromAddress(String user, String groups, String iuser, String address);
+
+ @Message(level=MessageLevel.ERROR, text="No proxy user or group configuration exists.")
+ void noProxyUserOrGroupConfigExists();
}
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 676cf4b..4d8c87b 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,6 +17,8 @@
*/
package org.apache.knox.gateway.util;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.security.Principal;
import java.util.Collections;
import java.util.List;
@@ -36,218 +38,243 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.UserGroupInformation;
-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";
- public static final String PROXYUSER_PREFIX = "hadoop.proxyuser";
- public static final String QUERY_PARAMETER_DOAS = "doAs";
- public static final String REAL_USER_NAME_ATTRIBUTE = "real.user.name";
- public static final String DO_GLOBAL_LOGOUT_ATTRIBUTE = "do.global.logout";
+ public static final String DEFAULT_AUTH_UNAUTHENTICATED_PATHS_PARAM = "/knoxtoken/api/v1/jwks.json";
+ public static final String PROXYUSER_PREFIX = "hadoop.proxyuser";
+ public static final String QUERY_PARAMETER_DOAS = "doAs";
+ public static final String REAL_USER_NAME_ATTRIBUTE = "real.user.name";
+ public static final String DO_GLOBAL_LOGOUT_ATTRIBUTE = "do.global.logout";
- 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();
+ 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
- * @param request
- * @return
- */
- public static boolean doesRequestContainUnauthPath(
- final Set<String> unAuthenticatedPaths, final ServletRequest request) {
- /* make sure the path matches EXACTLY to prevent auth bypass */
- return unAuthenticatedPaths.contains(((HttpServletRequest) request).getPathInfo());
- }
-
- /**
- * A helper method that parses a string and adds to the
- * provided unauthenticated set.
- * @param unAuthenticatedPaths
- * @param list
- */
- public static void parseStringThenAdd(final Set<String> unAuthenticatedPaths, final String list) {
- final StringTokenizer tokenizer = new StringTokenizer(list, ";,");
- while (tokenizer.hasMoreTokens()) {
- unAuthenticatedPaths.add(tokenizer.nextToken());
- }
- }
-
- /**
- * A method that parses a string (delimiters = ;,) and adds them to the
- * provided un-authenticated path set.
- * @param unAuthenticatedPaths
- * @param list
- * @param defaultList
- */
- public static void addUnauthPaths(final Set<String> unAuthenticatedPaths, final String list, final String defaultList) {
- /* add default unauthenticated paths list */
- parseStringThenAdd(unAuthenticatedPaths, defaultList);
- /* add provided unauthenticated paths list if specified */
- if (!StringUtils.isBlank(list)) {
- AuthFilterUtils.parseStringThenAdd(unAuthenticatedPaths, list);
- }
- }
-
- public static void refreshSuperUserGroupsConfiguration(ServletContext context, List<String> initParameterNames, String topologyName, String role) {
- if (context == null) {
- throw new IllegalArgumentException("Cannot get proxyuser configuration from NULL context");
- }
- refreshSuperUserGroupsConfiguration(context, null, initParameterNames, topologyName, role);
- }
-
- public static void refreshSuperUserGroupsConfiguration(FilterConfig filterConfig, List<String> initParameterNames, String topologyName, String role) {
- if (filterConfig == null) {
- throw new IllegalArgumentException("Cannot get proxyuser configuration from NULL filter config");
- }
- refreshSuperUserGroupsConfiguration(null, filterConfig, initParameterNames, topologyName, role);
- }
-
- private static void refreshSuperUserGroupsConfiguration(ServletContext context, FilterConfig filterConfig, List<String> initParameterNames, String topologyName, String role) {
- final Configuration conf = new Configuration(false);
- if (initParameterNames != null) {
- initParameterNames.stream().filter(name -> name.startsWith(PROXYUSER_PREFIX + ".")).forEach(name -> {
- String value = context == null ? filterConfig.getInitParameter(name) : context.getInitParameter(name);
- conf.set(name, value);
- });
+ public static final String PROXYGROUP_PREFIX = "hadoop.proxygroup";
+ /**
+ * A helper method that checks whether request contains
+ * unauthenticated path
+ * @param request
+ * @return
+ */
+ public static boolean doesRequestContainUnauthPath(
+ final Set<String> unAuthenticatedPaths, final ServletRequest request) {
+ /* make sure the path matches EXACTLY to prevent auth bypass */
+ return unAuthenticatedPaths.contains(((HttpServletRequest) request).getPathInfo());
}
- saveImpersonationProvider(topologyName, role, conf);
- }
-
- private static void saveImpersonationProvider(String topologyName, String role, final Configuration conf) {
- refreshSuperUserGroupsLock.lock();
- try {
- final ImpersonationProvider impersonationProvider = new DefaultImpersonationProvider();
- impersonationProvider.setConf(conf);
- impersonationProvider.init(PROXYUSER_PREFIX);
- LOG.createImpersonationProvider(topologyName, role, PROXYUSER_PREFIX, conf.getPropsWithPrefix(PROXYUSER_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, String topologyName, String role) throws AuthorizationException {
- return getProxyRequest(request, request.getUserPrincipal().getName(), doAsUser, topologyName, role);
- }
-
- public static HttpServletRequest getProxyRequest(HttpServletRequest request, String remoteUser, String doAsUser, String topologyName, String role) throws AuthorizationException {
- final UserGroupInformation remoteRequestUgi = getRemoteRequestUgi(remoteUser, doAsUser);
- if (remoteRequestUgi != null) {
- authorizeImpersonationRequest(request, remoteRequestUgi, topologyName, role);
-
- return new HttpServletRequestWrapper(request) {
- @Override
- public String getRemoteUser() {
- return remoteRequestUgi.getShortUserName();
+ /**
+ * A helper method that parses a string and adds to the
+ * provided unauthenticated set.
+ * @param unAuthenticatedPaths
+ * @param list
+ */
+ public static void parseStringThenAdd(final Set<String> unAuthenticatedPaths, final String list) {
+ final StringTokenizer tokenizer = new StringTokenizer(list, ";,");
+ while (tokenizer.hasMoreTokens()) {
+ unAuthenticatedPaths.add(tokenizer.nextToken());
}
+ }
- @Override
- public Principal getUserPrincipal() {
- return remoteRequestUgi::getUserName;
+ /**
+ * A method that parses a string (delimiters = ;,) and adds them to the
+ * provided un-authenticated path set.
+ * @param unAuthenticatedPaths
+ * @param list
+ * @param defaultList
+ */
+ public static void addUnauthPaths(final Set<String> unAuthenticatedPaths, final String list, final String defaultList) {
+ /* add default unauthenticated paths list */
+ parseStringThenAdd(unAuthenticatedPaths, defaultList);
+ /* add provided unauthenticated paths list if specified */
+ if (!StringUtils.isBlank(list)) {
+ AuthFilterUtils.parseStringThenAdd(unAuthenticatedPaths, list);
}
+ }
- @Override
- public Object getAttribute(String name) {
- if (name != null && name.equals(REAL_USER_NAME_ATTRIBUTE)) {
- return remoteRequestUgi.getRealUser().getShortUserName();
- } else {
- return super.getAttribute(name);
- }
+ public static void refreshSuperUserGroupsConfiguration(ServletContext context, List<String> initParameterNames, String topologyName, String role) {
+ if (context == null) {
+ throw new IllegalArgumentException("Cannot get proxyuser configuration from NULL context");
}
- };
-
+ refreshSuperUserGroupsConfiguration(context, null, initParameterNames, topologyName, role);
}
- return null;
- }
- public static void authorizeImpersonationRequest(HttpServletRequest request, String remoteUser, String doAsUser, String topologyName, String role) throws AuthorizationException {
- final UserGroupInformation remoteRequestUgi = getRemoteRequestUgi(remoteUser, doAsUser);
- if (remoteRequestUgi != null) {
- authorizeImpersonationRequest(request, remoteRequestUgi, topologyName, role);
+ public static void refreshSuperUserGroupsConfiguration(FilterConfig filterConfig, List<String> initParameterNames, String topologyName, String role) {
+ if (filterConfig == null) {
+ throw new IllegalArgumentException("Cannot get proxyuser configuration from NULL filter config");
+ }
+ refreshSuperUserGroupsConfiguration(null, filterConfig, initParameterNames, topologyName, role);
}
- }
- private static void authorizeImpersonationRequest(HttpServletRequest request, UserGroupInformation remoteRequestUgi, String topologyName, String role)
- throws AuthorizationException {
+ private static void refreshSuperUserGroupsConfiguration(ServletContext context, FilterConfig filterConfig, List<String> initParameterNames, String topologyName, String role) {
+ final Configuration conf = new Configuration(false);
+ boolean hasProxyGroupParams = false;
- final ImpersonationProvider impersonationProvider = getImpersonationProvider(topologyName, role);
-
- if (impersonationProvider != null) {
- try {
- impersonationProvider.authorize(remoteRequestUgi, request.getRemoteAddr());
- } catch (org.apache.hadoop.security.authorize.AuthorizationException e) {
- throw new AuthorizationException(e);
- }
- } else {
- throw new AuthorizationException("ImpersonationProvider for " + topologyName + " / " + role + " not found!");
+ if (initParameterNames != null) {
+ initParameterNames.stream().filter(name -> name.startsWith(PROXYUSER_PREFIX + ".")).forEach(name -> {
+ String value = context == null ? filterConfig.getInitParameter(name) : context.getInitParameter(name);
+ conf.set(name, value);
+ });
+ /* For proxy groups */
+ hasProxyGroupParams = initParameterNames.stream().anyMatch(name -> name.startsWith(PROXYGROUP_PREFIX + "."));
+ if(hasProxyGroupParams) {
+ initParameterNames.stream().filter(name -> name.startsWith(PROXYGROUP_PREFIX + ".")).forEach(name -> {
+ String value = context == null ? filterConfig.getInitParameter(name) : context.getInitParameter(name);
+ conf.set(name, value);
+ });
+ }
+ }
+ saveImpersonationProvider(topologyName, role, conf, new KnoxImpersonationProvider(), PROXYUSER_PREFIX);
}
- }
- 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();
+ private static void saveImpersonationProvider(String topologyName, String role, final Configuration conf, final ImpersonationProvider impersonationProvider, final String prefix) {
+ refreshSuperUserGroupsLock.lock();
+ try {
+ 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();
+ }
}
- return impersonationProvider;
- }
- private static UserGroupInformation getRemoteRequestUgi(String remoteUser, String doAsUser) {
- if (remoteUser != null) {
- final UserGroupInformation remoteUserUgi = UserGroupInformation.createRemoteUser(remoteUser);
- return UserGroupInformation.createProxyUser(doAsUser, remoteUserUgi);
+ public static HttpServletRequest getProxyRequest(HttpServletRequest request, String doAsUser, String topologyName, String role) throws AuthorizationException {
+ return getProxyRequest(request, request.getUserPrincipal().getName(), doAsUser, topologyName, role);
}
- return null;
- }
- public static boolean hasProxyConfig(String topologyName, String role) {
- return getImpersonationProvider(topologyName, role) != null;
- }
+ public static HttpServletRequest getProxyRequest(HttpServletRequest request, String remoteUser, String doAsUser, String topologyName, String role) throws AuthorizationException {
+ final UserGroupInformation remoteRequestUgi = getRemoteRequestUgi(remoteUser, doAsUser);
+ if (remoteRequestUgi != null) {
+ authorizeImpersonationRequest(request, remoteRequestUgi, topologyName, role);
- public static void removeProxyUserConfig(String topologyName, String role) {
- if (hasProxyConfig(topologyName, role)) {
- refreshSuperUserGroupsLock.lock();
- try {
- TOPOLOGY_IMPERSONATION_PROVIDERS.get(topologyName).remove(role);
- } finally {
- refreshSuperUserGroupsLock.unlock();
- }
+ return new HttpServletRequestWrapper(request) {
+ @Override
+ public String getRemoteUser() {
+ return remoteRequestUgi.getShortUserName();
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return remoteRequestUgi::getUserName;
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ if (name != null && name.equals(REAL_USER_NAME_ATTRIBUTE)) {
+ return remoteRequestUgi.getRealUser().getShortUserName();
+ } else {
+ return super.getAttribute(name);
+ }
+ }
+ };
+
+ }
+ return null;
}
- }
- /**
- * FilterConfig.getInitParameters() returns an enumeration and the first time we
- * iterate thru on its elements we can process the parameter names as desired
- * (because hasMoreElements returns true). The subsequent calls, however, will not
- * succeed because getInitParameters() returns the same object where the
- * hasMoreElements returns false.
- * <p>
- * In classes where there are multiple iterations should be conducted, a
- * collection should be used instead.
- *
- * @return the names of the filter's initialization parameters as a List of
- * String objects, or an empty List if the filter has no initialization
- * parameters.
- */
- public static List<String> getInitParameterNamesAsList(FilterConfig filterConfig) {
- return filterConfig.getInitParameterNames() == null ? Collections.emptyList() : Collections.list(filterConfig.getInitParameterNames());
- }
+ private static void authorizeImpersonationRequest(HttpServletRequest request, UserGroupInformation remoteRequestUgi, String topologyName, String role)
+ throws AuthorizationException {
+ authorizeImpersonationRequest(request, remoteRequestUgi, topologyName, role, Collections.emptyList());
+ }
- public static void markDoGlobalLogoutInRequest(HttpServletRequest request) {
- request.setAttribute(DO_GLOBAL_LOGOUT_ATTRIBUTE, "true");
- }
+ public static void authorizeImpersonationRequest(HttpServletRequest request, String remoteUser, String doAsUser, String topologyName, String role) throws AuthorizationException {
+ final UserGroupInformation remoteRequestUgi = getRemoteRequestUgi(remoteUser, doAsUser);
+ if (remoteRequestUgi != null) {
+ authorizeImpersonationRequest(request, remoteRequestUgi, topologyName, role, Collections.emptyList());
+ }
+ }
- public static boolean shouldDoGlobalLogout(HttpServletRequest request) {
- return request.getAttribute(DO_GLOBAL_LOGOUT_ATTRIBUTE) == null ? false : Boolean.parseBoolean((String) request.getAttribute(DO_GLOBAL_LOGOUT_ATTRIBUTE));
- }
+ public static void authorizeImpersonationRequest(HttpServletRequest request, String remoteUser, String doAsUser, String topologyName, String role, List<String> groups) throws AuthorizationException {
+ final UserGroupInformation remoteRequestUgi = getRemoteRequestUgi(remoteUser, doAsUser);
+ if (remoteRequestUgi != null) {
+ authorizeImpersonationRequest(request, remoteRequestUgi, topologyName, role, groups);
+ }
+ }
+
+ private static void authorizeImpersonationRequest(HttpServletRequest request, UserGroupInformation remoteRequestUgi, String topologyName, String role, List<String> groups)
+ throws AuthorizationException {
+
+ final ImpersonationProvider impersonationProvider = getImpersonationProvider(topologyName, role);
+
+ if (impersonationProvider != null) {
+ try {
+ if (impersonationProvider instanceof KnoxImpersonationProvider) {
+ ((KnoxImpersonationProvider) impersonationProvider).authorize(remoteRequestUgi, InetAddress.getByName(request.getRemoteAddr()), groups);
+ } else {
+ impersonationProvider.authorize(remoteRequestUgi, request.getRemoteAddr());
+ }
+
+ } catch (org.apache.hadoop.security.authorize.AuthorizationException | UnknownHostException e) {
+ throw new AuthorizationException(e);
+ }
+ } 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(String remoteUser, String doAsUser) {
+ if (remoteUser != null) {
+ final UserGroupInformation remoteUserUgi = UserGroupInformation.createRemoteUser(remoteUser);
+ return UserGroupInformation.createProxyUser(doAsUser, remoteUserUgi);
+ }
+ return null;
+ }
+
+ public static boolean hasProxyConfig(String topologyName, String role) {
+ return getImpersonationProvider(topologyName, role) != null;
+ }
+
+ public static void removeProxyUserConfig(String topologyName, String role) {
+ if (hasProxyConfig(topologyName, role)) {
+ refreshSuperUserGroupsLock.lock();
+ try {
+ TOPOLOGY_IMPERSONATION_PROVIDERS.get(topologyName).remove(role);
+ } finally {
+ refreshSuperUserGroupsLock.unlock();
+ }
+ }
+ }
+
+ /**
+ * FilterConfig.getInitParameters() returns an enumeration and the first time we
+ * iterate thru on its elements we can process the parameter names as desired
+ * (because hasMoreElements returns true). The subsequent calls, however, will not
+ * succeed because getInitParameters() returns the same object where the
+ * hasMoreElements returns false.
+ * <p>
+ * In classes where there are multiple iterations should be conducted, a
+ * collection should be used instead.
+ *
+ * @return the names of the filter's initialization parameters as a List of
+ * String objects, or an empty List if the filter has no initialization
+ * parameters.
+ */
+ public static List<String> getInitParameterNamesAsList(FilterConfig filterConfig) {
+ return filterConfig.getInitParameterNames() == null ? Collections.emptyList() : Collections.list(filterConfig.getInitParameterNames());
+ }
+
+ public static void markDoGlobalLogoutInRequest(HttpServletRequest request) {
+ request.setAttribute(DO_GLOBAL_LOGOUT_ATTRIBUTE, "true");
+ }
+
+ public static boolean shouldDoGlobalLogout(HttpServletRequest request) {
+ return request.getAttribute(DO_GLOBAL_LOGOUT_ATTRIBUTE) == null ? false : Boolean.parseBoolean((String) request.getAttribute(DO_GLOBAL_LOGOUT_ATTRIBUTE));
+ }
}
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/util/KnoxImpersonationProvider.java b/gateway-spi/src/main/java/org/apache/knox/gateway/util/KnoxImpersonationProvider.java
new file mode 100644
index 0000000..df1d9a1
--- /dev/null
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/util/KnoxImpersonationProvider.java
@@ -0,0 +1,295 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.util;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.authorize.AccessControlList;
+import org.apache.hadoop.security.authorize.AuthorizationException;
+import org.apache.hadoop.security.authorize.DefaultImpersonationProvider;
+import org.apache.hadoop.util.MachineList;
+import org.apache.knox.gateway.i18n.GatewaySpiMessages;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+
+import java.net.InetAddress;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static org.apache.knox.gateway.util.AuthFilterUtils.PROXYGROUP_PREFIX;
+import static org.apache.knox.gateway.util.AuthFilterUtils.PROXYUSER_PREFIX;
+
+/**
+ * An extension of Hadoop's DefaultImpersonationProvider that adds support for group-based impersonation.
+ * This provider allows users who belong to specific groups to impersonate other users.
+ */
+public class KnoxImpersonationProvider extends DefaultImpersonationProvider {
+ private static final GatewaySpiMessages LOG = MessagesFactory.get(GatewaySpiMessages.class);
+ private static final String CONF_HOSTS = ".hosts";
+ private static final String CONF_USERS = ".users";
+ private static final String CONF_GROUPS = ".groups";
+ private static final String PREFIX_REGEX_EXP = "\\.";
+ private static final String USERS_GROUPS_REGEX_EXP = "[\\S]*(" +
+ Pattern.quote(CONF_USERS) + "|" + Pattern.quote(CONF_GROUPS) + ")";
+ private static final String HOSTS_REGEX_EXP = "[\\S]*" + Pattern.quote(CONF_HOSTS);
+ private final Map<String, AccessControlList> proxyGroupsAcls = new HashMap<>();
+ private Map<String, MachineList> groupProxyHosts = new HashMap<>();
+ private String groupConfigPrefix;
+ private boolean doesProxyUserConfigExist = true;
+ private boolean doesProxyGroupConfigExist;
+ static final String IMPERSONATION_ENABLED_PARAM = ".impersonation.enabled";
+
+ public KnoxImpersonationProvider() {
+ super();
+ }
+
+ @Override
+ public Configuration getConf() {
+ return super.getConf();
+ }
+
+ @Override
+ public void setConf(Configuration conf) {
+ super.setConf(conf);
+ }
+
+ @Override
+ public void init(String configurationPrefix) {
+ super.init(configurationPrefix);
+
+ /* Check if user proxy configs are provided */
+ doesProxyUserConfigExist = !getFilteredProps(PROXYUSER_PREFIX).isEmpty();
+
+ /* Check if group proxy configs are provided */
+ doesProxyGroupConfigExist = !getFilteredProps(PROXYGROUP_PREFIX).isEmpty();
+
+ initGroupBasedProvider(PROXYGROUP_PREFIX);
+ }
+
+ /**
+ * Returns a filtered map of properties with the specified prefix,
+ * excluding the impersonation enabled parameter.
+ *
+ * @param prefix the prefix to filter properties by
+ * @return a map of filtered properties
+ */
+ private Map<String, String> getFilteredProps(String prefix) {
+ return Optional.ofNullable(getConf().getPropsWithPrefix(prefix))
+ .orElse(Collections.emptyMap())
+ .entrySet()
+ .stream()
+ .filter(entry -> !IMPERSONATION_ENABLED_PARAM.equals(entry.getKey()))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
+ private void initGroupBasedProvider(final String proxyGroupPrefix) {
+ groupConfigPrefix = proxyGroupPrefix +
+ (proxyGroupPrefix.endsWith(".") ? "" : ".");
+
+ String prefixRegEx = groupConfigPrefix.replace(".", PREFIX_REGEX_EXP);
+ String usersGroupsRegEx = prefixRegEx + USERS_GROUPS_REGEX_EXP;
+ String hostsRegEx = prefixRegEx + HOSTS_REGEX_EXP;
+
+ // get list of users and groups per proxygroup
+ // Map of <hadoop.proxygroup.[VIRTUAL_GROUP].users|groups, group1,group2>
+ Map<String, String> allMatchKeys =
+ getConf().getValByRegex(usersGroupsRegEx);
+
+ for (Map.Entry<String, String> entry : allMatchKeys.entrySet()) {
+ //aclKey = hadoop.proxygroup.[VIRTUAL_GROUP]
+ String aclKey = getAclKey(entry.getKey());
+
+ if (!proxyGroupsAcls.containsKey(aclKey)) {
+ proxyGroupsAcls.put(aclKey, new AccessControlList(
+ allMatchKeys.get(aclKey + CONF_USERS),
+ allMatchKeys.get(aclKey + CONF_GROUPS)));
+ }
+ }
+
+ // get hosts per proxygroup
+ allMatchKeys = getConf().getValByRegex(hostsRegEx);
+ for (Map.Entry<String, String> entry : allMatchKeys.entrySet()) {
+ groupProxyHosts.put(entry.getKey(),
+ new MachineList(entry.getValue()));
+ }
+ }
+
+ private String getAclKey(String key) {
+ int endIndex = key.lastIndexOf('.');
+ if (endIndex != -1) {
+ return key.substring(0, endIndex);
+ }
+ return key;
+ }
+
+ /**
+ * Authorization based on user and group impersonation policies.
+ *
+ * @param user the user information attempting the operation, which includes the real
+ * user and the effective impersonated user.
+ * @param remoteAddress the remote address from which the user is connecting.
+ * @throws AuthorizationException if the user is not authorized based on the
+ * configured impersonation and group policies.
+ */
+ @Override
+ public void authorize(UserGroupInformation user, InetAddress remoteAddress) throws AuthorizationException {
+ authorize(user, remoteAddress, Collections.emptyList());
+ }
+
+ /**
+ * Authorization based on groups that are provided as a function argument
+ *
+ * @param user the user information attempting the operation, which includes the real
+ * user and the effective impersonated user.
+ * @param groups the list of groups to check for authorization.
+ * @param remoteAddress the remote address from which the user is connecting.
+ * @throws AuthorizationException if the user is not authorized based on the
+ * configured impersonation and group policies.
+ */
+ public void authorize(UserGroupInformation user, InetAddress remoteAddress, List<String> groups) throws AuthorizationException {
+
+ /*
+ * There are few cases to consider here:
+ * 1. If proxy user config exists and there is impersonation failure:
+ * 1.1 if proxy group config exists, try to authorize using groups
+ * 1.2 if proxy group config does not exist, throw the original exception
+ * 2. If proxy user config exists and there is impersonation success:
+ * 2.2 Do not check for proxy group auth
+ * 3. If proxy user config does not exist:
+ * 3.1 Check for proxy group config and return success or failure based on the results.
+ */
+ if (doesProxyUserConfigExist) {
+ try{
+ /* check for proxy user authorization */
+ super.authorize(user, remoteAddress);
+ } catch (final AuthorizationException e) {
+ /*
+ * Log and try group based impersonation.
+ * Since this provider is for groups no need to check if
+ * proxy group config exists, we know it does.
+ */
+ LOG.failedToImpersonateUserTryingGroups(user.getUserName(), e.toString());
+ /* check for proxy group authorization */
+ if(doesProxyGroupConfigExist) {
+ checkProxyGroupAuthorization(user, remoteAddress, groups);
+ } else {
+ /* If proxy group config does not exist, throw the original exception */
+ throw e;
+ }
+ }
+ } else if (doesProxyGroupConfigExist) {
+ /* check for proxy group authorization */
+ checkProxyGroupAuthorization(user, remoteAddress, groups);
+ } else {
+ /* If no proxy user or group config exists, throw an exception */
+ LOG.noProxyUserOrGroupConfigExists();
+ throw new AuthorizationException("User: " + user.getRealUser()
+ + " is not allowed to impersonate " + user.getUserName());
+ }
+ }
+
+
+ /**
+ * Helper method to check if the group a given user belongs to is authorized to impersonate
+ * Returns true if the user is authorized, false otherwise.
+ *
+ * @param user
+ * @param remoteAddress
+ * @return
+ */
+ private void checkProxyGroupAuthorization(final UserGroupInformation user, final InetAddress remoteAddress, List<String> groups) throws AuthorizationException {
+ if (user == null) {
+ throw new IllegalArgumentException("user is null.");
+ }
+
+ final UserGroupInformation realUser = user.getRealUser();
+ if (realUser == null) {
+ return;
+ }
+
+ // Get the real user's groups (both real and virtual)
+ Set<String> realUserGroups = new HashSet<>();
+ /* Add provided groups */
+ if(groups != null && !groups.isEmpty()) {
+ realUserGroups.addAll(groups);
+ }
+ /* Add groups from subject */
+ if (user.getRealUser().getGroupNames() != null) {
+ Collections.addAll(realUserGroups, user.getRealUser().getGroupNames());
+ }
+
+ boolean proxyGroupFound = false;
+ // Check if any of the real user's groups have permission to impersonate the proxy user
+ proxyGroupFound = isProxyGroupFound(user, realUserGroups, proxyGroupFound);
+
+ if (!proxyGroupFound) {
+ LOG.failedToImpersonateGroups(realUser.getUserName(), realUserGroups.toString(), user.getUserName());
+ throw new AuthorizationException("User: " + realUser.getUserName()
+ + " with groups " + realUserGroups.toString()
+ + " is not allowed to impersonate " + user.getUserName());
+ }
+
+ boolean proxyGroupHostFound = false;
+ proxyGroupHostFound = isProxyGroupHostFound(remoteAddress, realUserGroups, proxyGroupHostFound);
+
+ if (!proxyGroupHostFound) {
+ LOG.failedToImpersonateGroupsFromAddress(realUser.getUserName(), realUserGroups.toString(), user.getUserName(), remoteAddress.toString());
+ throw new AuthorizationException("User: " + realUser.getUserName()
+ + " with groups " + realUserGroups.toString()
+ + " from address " + remoteAddress.toString()
+ + " is not allowed to impersonate " + user.getUserName());
+ }
+
+ /* all checks pass */
+ }
+
+ private boolean isProxyGroupHostFound(InetAddress remoteAddress, Set<String> realUserGroups, boolean proxyGroupHostFound) {
+ for (final String group : realUserGroups) {
+ final MachineList machineList = groupProxyHosts.get(groupConfigPrefix + group + CONF_HOSTS);
+
+ if (machineList == null || !machineList.includes(remoteAddress)) {
+ continue;
+ } else {
+ proxyGroupHostFound = true;
+ break;
+ }
+ }
+ return proxyGroupHostFound;
+ }
+
+ private boolean isProxyGroupFound(UserGroupInformation user, Set<String> realUserGroups, boolean proxyGroupFound) {
+ for (String group : realUserGroups) {
+ final AccessControlList acl = proxyGroupsAcls.get(groupConfigPrefix +
+ group);
+
+ if (acl == null || !acl.isUserAllowed(user)) {
+ continue;
+ } else {
+ proxyGroupFound = true;
+ break;
+ }
+ }
+ return proxyGroupFound;
+ }
+
+}
diff --git a/gateway-spi/src/test/java/org/apache/knox/gateway/util/KnoxImpersonationProviderTest.java b/gateway-spi/src/test/java/org/apache/knox/gateway/util/KnoxImpersonationProviderTest.java
new file mode 100644
index 0000000..7d39443
--- /dev/null
+++ b/gateway-spi/src/test/java/org/apache/knox/gateway/util/KnoxImpersonationProviderTest.java
@@ -0,0 +1,725 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.util;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Timeout;
+import org.mockito.Mockito;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+
+
+import static org.apache.knox.gateway.util.AuthFilterUtils.PROXYUSER_PREFIX;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+public class KnoxImpersonationProviderTest {
+
+ @Rule
+ public Timeout globalTimeout = new Timeout(10000, TimeUnit.MILLISECONDS);
+ private KnoxImpersonationProvider provider;
+ private Configuration config;
+
+ @Before
+ public void setUp() {
+
+ provider = new KnoxImpersonationProvider();
+ config = new Configuration();
+
+ // Setup proxy user configuration
+ config.set("hadoop.proxyuser.testuser.users", "*");
+ config.set("hadoop.proxyuser.testuser.groups", "*");
+ config.set("hadoop.proxyuser.testuser.hosts", "*");
+
+ // Setup 3 proxy groups
+ config.set("hadoop.proxygroup.virtual_group_1.users", "*");
+ config.set("hadoop.proxygroup.virtual_group_1.groups", "*");
+ config.set("hadoop.proxygroup.virtual_group_1.hosts", "*");
+ config.set("hadoop.proxygroup.virtual.group_2.users", "*");
+ config.set("hadoop.proxygroup.virtual.group_2.groups", "*");
+ config.set("hadoop.proxygroup.virtual.group_2.hosts", "*");
+ config.set("hadoop.proxygroup.virtual group_3.users", "*");
+ config.set("hadoop.proxygroup.virtual group_3.groups", "*");
+ config.set("hadoop.proxygroup.virtual group_3.hosts", "*");
+
+ provider.setConf(config);
+ provider.init(PROXYUSER_PREFIX);
+ }
+
+ @Test
+ public void testGetConf() {
+ assertEquals(config, provider.getConf());
+ }
+
+ @Test
+ public void testSetConf() {
+ Configuration newConfig = new Configuration(false);
+ provider.setConf(newConfig);
+ assertEquals(newConfig, provider.getConf());
+ }
+
+ @Test
+ public void testInitWithEmptyConfig() {
+ provider.init("hadoop.proxygroup");
+ // No exception should be thrown
+ }
+
+ @Test
+ public void testInitWithValidConfig() {
+ // Set up configuration with valid proxy groups
+ config.set("hadoop.proxygroup.admin.users", "user1,user2");
+ config.set("hadoop.proxygroup.admin.groups", "group1,group2");
+ config.set("hadoop.proxygroup.admin.hosts", "host1,host2");
+
+ provider.init("hadoop.proxygroup");
+ // No exception should be thrown
+ }
+
+ @Test
+ public void testGetAclKey() throws Exception {
+ // Test the private getAclKey method using reflection
+ java.lang.reflect.Method method = KnoxImpersonationProvider.class.getDeclaredMethod("getAclKey", String.class);
+ method.setAccessible(true);
+
+ assertEquals("hadoop.proxygroup.admin", method.invoke(provider, "hadoop.proxygroup.admin.users"));
+ assertEquals("hadoop.proxygroup.admin", method.invoke(provider, "hadoop.proxygroup.admin.groups"));
+ assertEquals("key", method.invoke(provider, "key"));
+ }
+
+ @Test
+ public void testAuthorizationSuccess() throws Exception {
+ String proxyUser = "testuser";
+ String impersonatedUser = "impersonatedUser";
+ String[] proxyGroups = {"virtual_group_1"};
+
+ UserGroupInformation realUserUGI = Mockito.mock(UserGroupInformation.class);
+ UserGroupInformation userGroupInformation = Mockito.mock(UserGroupInformation.class);
+
+ when(realUserUGI.getShortUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getGroupNames()).thenReturn(proxyGroups);
+
+ when(userGroupInformation.getRealUser()).thenReturn(realUserUGI);
+ when(userGroupInformation.getUserName()).thenReturn(impersonatedUser);
+
+ // Use reflection to call the checkProxyGroupAuthorization method directly
+ java.lang.reflect.Method method = KnoxImpersonationProvider.class.getDeclaredMethod(
+ "checkProxyGroupAuthorization",
+ UserGroupInformation.class,
+ InetAddress.class,
+ List.class);
+ method.setAccessible(true);
+ method.invoke(provider, userGroupInformation, InetAddress.getByName("2.2.2.2"), Collections.emptyList());
+
+ String[] proxyGroups2 = {"virtual.group_2"};
+ when(realUserUGI.getShortUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getGroupNames()).thenReturn(proxyGroups2);
+ when(userGroupInformation.getRealUser()).thenReturn(realUserUGI);
+ when(userGroupInformation.getUserName()).thenReturn(impersonatedUser);
+
+ // Use reflection to call the checkProxyGroupAuthorization method directly
+ method.invoke(provider, userGroupInformation, InetAddress.getByName("2.2.2.2"), Collections.emptyList());
+ }
+
+ /**
+ * Test the case where proxy user is disabled and only proxy groups is enabled.
+ * i.e.
+ * hadoop.proxyuser.impersonation.enabled = false
+ * hadoop.proxygroup.impersonation.enabled = true
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testAuthorizationSuccessWithOnlyProxyGroupsConfigured() throws Exception {
+ KnoxImpersonationProvider gProvider = new KnoxImpersonationProvider();
+ Configuration gConfig = new Configuration();
+
+ String proxyUser = "testuser";
+ String impersonatedUser = "impersonatedUser";
+
+ // Setup proxy user configuration
+ gConfig.set("hadoop.proxyuser." + proxyUser + ".users", "*");
+ gConfig.set("hadoop.proxyuser." + proxyUser + ".hosts", "*");
+
+ // Setup 3 proxy groups
+ gConfig.set("hadoop.proxygroup.virtual_group_1.users", "*");
+ gConfig.set("hadoop.proxygroup.virtual_group_1.groups", "*");
+ gConfig.set("hadoop.proxygroup.virtual_group_1.hosts", "*");
+ gConfig.set("hadoop.proxygroup.virtual.group_2.users", "*");
+ gConfig.set("hadoop.proxygroup.virtual.group_2.groups", "*");
+ gConfig.set("hadoop.proxygroup.virtual.group_2.hosts", "*");
+ gConfig.set("hadoop.proxygroup.virtual group_3.users", "*");
+ gConfig.set("hadoop.proxygroup.virtual group_3.groups", "*");
+ gConfig.set("hadoop.proxygroup.virtual group_3.hosts", "*");
+
+ gProvider.setConf(gConfig);
+ // Initialize with both prefixes to enable both proxy user and proxy group authorization
+ gProvider.init(PROXYUSER_PREFIX);
+
+ String[] proxyGroups = {"virtual_group_1"};
+
+ UserGroupInformation realUserUGI = Mockito.mock(UserGroupInformation.class);
+ UserGroupInformation userGroupInformation = Mockito.mock(UserGroupInformation.class);
+
+ when(realUserUGI.getShortUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getGroupNames()).thenReturn(proxyGroups);
+
+ when(userGroupInformation.getRealUser()).thenReturn(realUserUGI);
+ when(userGroupInformation.getUserName()).thenReturn(impersonatedUser);
+
+ // Use reflection to call the checkProxyGroupAuthorization method directly
+ java.lang.reflect.Method method = KnoxImpersonationProvider.class.getDeclaredMethod(
+ "checkProxyGroupAuthorization",
+ UserGroupInformation.class,
+ InetAddress.class,
+ List.class);
+ method.setAccessible(true);
+ method.invoke(gProvider, userGroupInformation, InetAddress.getByName("2.2.2.2"), Collections.emptyList());
+
+ String[] proxyGroups2 = {"virtual.group_2"};
+ when(realUserUGI.getShortUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getGroupNames()).thenReturn(proxyGroups2);
+ when(userGroupInformation.getRealUser()).thenReturn(realUserUGI);
+ when(userGroupInformation.getUserName()).thenReturn(impersonatedUser);
+
+ // Use reflection to call the checkProxyGroupAuthorization method directly
+ method.invoke(gProvider, userGroupInformation, InetAddress.getByName("2.2.2.2"), Collections.emptyList());
+ }
+
+ @Test(expected = org.apache.hadoop.security.authorize.AuthorizationException.class)
+ public void testAuthorizationFailure() throws Exception {
+ String proxyUser = "dummyUser";
+ String impersonatedUser = "impersonatedUser";
+ String[] proxyGroups = {"virtual group_3"};
+
+ UserGroupInformation realUserUGI = Mockito.mock(UserGroupInformation.class);
+ UserGroupInformation userGroupInformation = Mockito.mock(UserGroupInformation.class);
+
+ when(realUserUGI.getUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getGroupNames()).thenReturn(proxyGroups);
+
+ when(userGroupInformation.getRealUser()).thenReturn(realUserUGI);
+ when(userGroupInformation.getUserName()).thenReturn(impersonatedUser);
+ provider.authorize(userGroupInformation, InetAddress.getByName("2.2.2.2"));
+ }
+
+ /**
+ * Test the case where both proxy user and proxy group are disabled.
+ * Authorization should succeed in this case.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testAuthorizationSuccessWithBothProxyMethodsDisabled() throws Exception {
+
+ KnoxImpersonationProvider gProvider = new KnoxImpersonationProvider();
+ Configuration gConfig = new Configuration();
+
+ String proxyUser = "testuser";
+ String impersonatedUser = "impersonatedUser";
+
+ // Setup proxy user configuration
+ gConfig.set("hadoop.proxyuser." + proxyUser + ".users", "*");
+ gConfig.set("hadoop.proxyuser." + proxyUser + ".hosts", "*");
+
+ // Setup proxy group configuration
+ gConfig.set("hadoop.proxygroup.virtual_group_1.users", "*");
+ gConfig.set("hadoop.proxygroup.virtual_group_1.groups", "*");
+ gConfig.set("hadoop.proxygroup.virtual_group_1.hosts", "*");
+
+ gProvider.setConf(gConfig);
+ gProvider.init(PROXYUSER_PREFIX);
+
+ String[] proxyGroups = {"virtual_group_1"};
+
+ UserGroupInformation realUserUGI = Mockito.mock(UserGroupInformation.class);
+ UserGroupInformation userGroupInformation = Mockito.mock(UserGroupInformation.class);
+
+ when(realUserUGI.getShortUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getGroupNames()).thenReturn(proxyGroups);
+
+ when(userGroupInformation.getRealUser()).thenReturn(realUserUGI);
+ when(userGroupInformation.getUserName()).thenReturn(impersonatedUser);
+
+ // Use reflection to call the checkProxyGroupAuthorization method directly
+ java.lang.reflect.Method method = KnoxImpersonationProvider.class.getDeclaredMethod(
+ "checkProxyGroupAuthorization",
+ UserGroupInformation.class,
+ InetAddress.class,
+ List.class);
+ method.setAccessible(true);
+ method.invoke(gProvider, userGroupInformation, InetAddress.getByName("2.2.2.2"), Collections.emptyList());
+ }
+
+ /**
+ * Test the case where only proxy user is enabled and proxy group is disabled.
+ * i.e.
+ * hadoop.proxyuser.impersonation.enabled = true
+ * hadoop.proxygroup.impersonation.enabled = false
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testAuthorizationSuccessWithOnlyProxyUserConfigured() throws Exception {
+ KnoxImpersonationProvider gProvider = new KnoxImpersonationProvider();
+ Configuration gConfig = new Configuration();
+
+ String proxyUser = "testuser";
+ String impersonatedUser = "impersonatedUser";
+
+ // Setup proxy user configuration
+ gConfig.set("hadoop.proxyuser." + proxyUser + ".users", "*");
+ gConfig.set("hadoop.proxyuser." + proxyUser + ".groups", "*");
+ gConfig.set("hadoop.proxyuser." + proxyUser + ".hosts", "*");
+
+ // Setup proxy group configuration for the group the user belongs to
+ gConfig.set("hadoop.proxygroup.somegroup.users", "*");
+ gConfig.set("hadoop.proxygroup.somegroup.groups", "*");
+ gConfig.set("hadoop.proxygroup.somegroup.hosts", "*");
+
+ gProvider.setConf(gConfig);
+ gProvider.init(PROXYUSER_PREFIX);
+
+ String[] proxyGroups = {"somegroup"};
+
+ UserGroupInformation realUserUGI = Mockito.mock(UserGroupInformation.class);
+ UserGroupInformation userGroupInformation = Mockito.mock(UserGroupInformation.class);
+
+ when(realUserUGI.getShortUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getGroupNames()).thenReturn(proxyGroups);
+
+ when(userGroupInformation.getRealUser()).thenReturn(realUserUGI);
+ when(userGroupInformation.getUserName()).thenReturn(impersonatedUser);
+
+ // Use reflection to call the checkProxyGroupAuthorization method directly
+ java.lang.reflect.Method method = KnoxImpersonationProvider.class.getDeclaredMethod(
+ "checkProxyGroupAuthorization",
+ UserGroupInformation.class,
+ InetAddress.class,
+ List.class);
+ method.setAccessible(true);
+ method.invoke(gProvider, userGroupInformation, InetAddress.getByName("2.2.2.2"), Collections.emptyList());
+ }
+
+ /**
+ * Test the case where both proxy user and proxy group are enabled with AND mode.
+ * This ensures that both authorization methods are actually being used.
+ * i.e.
+ * hadoop.proxyuser.impersonation.enabled = true
+ * hadoop.proxygroup.impersonation.enabled = true
+ * impersonation.mode = AND
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testAuthorizationSuccessWithBothProxyMethodsEnabledAndMode() throws Exception {
+ KnoxImpersonationProvider gProvider = new KnoxImpersonationProvider();
+ Configuration gConfig = new Configuration();
+
+ // Set impersonation mode to AND
+ gConfig.set("impersonation.mode", "AND");
+
+ String proxyUser = "testuser";
+ String impersonatedUser = "impersonatedUser";
+
+ // Setup proxy user configuration
+ gConfig.set("hadoop.proxyuser." + proxyUser + ".users", "*");
+ gConfig.set("hadoop.proxyuser." + proxyUser + ".groups", "*");
+ gConfig.set("hadoop.proxyuser." + proxyUser + ".hosts", "*");
+
+ // Setup proxy group configuration
+ gConfig.set("hadoop.proxygroup.virtual_group_1.users", "*");
+ gConfig.set("hadoop.proxygroup.virtual_group_1.groups", "*");
+ gConfig.set("hadoop.proxygroup.virtual_group_1.hosts", "*");
+
+ gProvider.setConf(gConfig);
+ gProvider.init(PROXYUSER_PREFIX);
+
+ String[] proxyGroups = {"virtual_group_1"};
+
+ UserGroupInformation realUserUGI = Mockito.mock(UserGroupInformation.class);
+ UserGroupInformation userGroupInformation = Mockito.mock(UserGroupInformation.class);
+
+ when(realUserUGI.getShortUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getGroupNames()).thenReturn(proxyGroups);
+
+ when(userGroupInformation.getRealUser()).thenReturn(realUserUGI);
+ when(userGroupInformation.getUserName()).thenReturn(impersonatedUser);
+
+ // Use reflection to call the checkProxyGroupAuthorization method directly
+ java.lang.reflect.Method method = KnoxImpersonationProvider.class.getDeclaredMethod(
+ "checkProxyGroupAuthorization",
+ UserGroupInformation.class,
+ InetAddress.class,
+ List.class);
+ method.setAccessible(true);
+ method.invoke(gProvider, userGroupInformation, InetAddress.getByName("2.2.2.2"), Collections.emptyList());
+ }
+
+ /**
+ * Test the case where authorization is successful using the new authorize method
+ * that accepts a list of groups as a parameter.
+ *
+ * @throws org.apache.hadoop.security.authorize.AuthorizationException
+ * @throws UnknownHostException
+ */
+ @Test
+ public void testAuthorizationSuccessWithProvidedGroups() throws org.apache.hadoop.security.authorize.AuthorizationException, UnknownHostException {
+ KnoxImpersonationProvider gProvider = new KnoxImpersonationProvider();
+ Configuration gConfig = new Configuration();
+
+ // Setup proxy group configuration
+ gConfig.set("hadoop.proxygroup.virtual_group_1.groups", "*");
+ gConfig.set("hadoop.proxygroup.virtual_group_1.hosts", "*");
+
+ gProvider.setConf(gConfig);
+ gProvider.init(PROXYUSER_PREFIX);
+
+ String proxyUser = "testuser";
+ // User has no groups in their subject
+ String[] emptyGroups = {};
+
+ UserGroupInformation realUserUGI = Mockito.mock(UserGroupInformation.class);
+ UserGroupInformation userGroupInformation = Mockito.mock(UserGroupInformation.class);
+
+ when(realUserUGI.getShortUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getGroupNames()).thenReturn(emptyGroups);
+
+ when(userGroupInformation.getRealUser()).thenReturn(realUserUGI);
+ when(userGroupInformation.getUserName()).thenReturn("impersonatedUser");
+
+ // Create a list of groups to provide to the authorize method
+ List<String> providedGroups = new ArrayList<>();
+ providedGroups.add("virtual_group_1");
+
+ // This should not throw an exception since the provided groups include virtual_group_1
+ // which is authorized to impersonate
+ gProvider.authorize(userGroupInformation, InetAddress.getByName("2.2.2.2"), providedGroups);
+ }
+
+ /**
+ * Test the case where authorization fails when the provided groups
+ * do not have permission to impersonate.
+ *
+ * @throws org.apache.hadoop.security.authorize.AuthorizationException
+ * @throws UnknownHostException
+ */
+ @Test(expected = org.apache.hadoop.security.authorize.AuthorizationException.class)
+ public void testAuthorizationFailureWithProvidedGroups() throws org.apache.hadoop.security.authorize.AuthorizationException, UnknownHostException {
+ KnoxImpersonationProvider gProvider = new KnoxImpersonationProvider();
+ Configuration gConfig = new Configuration();
+
+ // Setup proxy group configuration
+ gConfig.set("hadoop.proxygroup.virtual_group_1.groups", "*");
+ gConfig.set("hadoop.proxygroup.virtual_group_1.hosts", "*");
+
+ gProvider.setConf(gConfig);
+ gProvider.init(PROXYUSER_PREFIX);
+
+ String proxyUser = "testuser";
+ // User has no groups in their subject
+ String[] emptyGroups = {};
+
+ UserGroupInformation realUserUGI = Mockito.mock(UserGroupInformation.class);
+ UserGroupInformation userGroupInformation = Mockito.mock(UserGroupInformation.class);
+
+ when(realUserUGI.getShortUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getGroupNames()).thenReturn(emptyGroups);
+
+ when(userGroupInformation.getRealUser()).thenReturn(realUserUGI);
+ when(userGroupInformation.getUserName()).thenReturn("impersonatedUser");
+
+ // Create a list of groups to provide to the authorize method
+ // These groups do not have permission to impersonate
+ List<String> providedGroups = new ArrayList<>();
+ providedGroups.add("unauthorized_group");
+
+ // This should throw an exception since the provided groups do not include any
+ // that are authorized to impersonate
+ gProvider.authorize(userGroupInformation, InetAddress.getByName("2.2.2.2"), providedGroups);
+ }
+
+ /**
+ * Test the case where authorization succeeds when the user's subject groups
+ * combined with the provided groups include one that has permission to impersonate.
+ *
+ * @throws org.apache.hadoop.security.authorize.AuthorizationException
+ * @throws UnknownHostException
+ */
+ @Test
+ public void testAuthorizationSuccessWithCombinedGroups() throws org.apache.hadoop.security.authorize.AuthorizationException, UnknownHostException {
+ KnoxImpersonationProvider gProvider = new KnoxImpersonationProvider();
+ Configuration gConfig = new Configuration();
+
+ String proxyUser = "testuser";
+ String impersonatedUser = "impersonatedUser";
+
+ // Setup proxy user configuration
+ gConfig.set("hadoop.proxyuser." + proxyUser + ".users", "*");
+ gConfig.set("hadoop.proxyuser." + proxyUser + ".groups", "*");
+ gConfig.set("hadoop.proxyuser." + proxyUser + ".hosts", "*");
+
+ // Setup proxy group configuration
+ gConfig.set("hadoop.proxygroup.virtual_group_1.users", "*");
+ gConfig.set("hadoop.proxygroup.virtual_group_1.groups", "*");
+ gConfig.set("hadoop.proxygroup.virtual_group_1.hosts", "*");
+ gConfig.set("hadoop.proxygroup.virtual_group_2.users", "*");
+ gConfig.set("hadoop.proxygroup.virtual_group_2.groups", "*");
+ gConfig.set("hadoop.proxygroup.virtual_group_2.hosts", "*");
+
+ gProvider.setConf(gConfig);
+ gProvider.init(PROXYUSER_PREFIX);
+
+ // User has virtual_group_1 in their subject
+ String[] subjectGroups = {"virtual_group_1"};
+
+ UserGroupInformation realUserUGI = Mockito.mock(UserGroupInformation.class);
+ UserGroupInformation userGroupInformation = Mockito.mock(UserGroupInformation.class);
+
+ when(realUserUGI.getShortUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getGroupNames()).thenReturn(subjectGroups);
+
+ when(userGroupInformation.getRealUser()).thenReturn(realUserUGI);
+ when(userGroupInformation.getUserName()).thenReturn(impersonatedUser);
+
+ // Create a list of groups to provide to the authorize method
+ // These include virtual_group_2 which is also authorized
+ List<String> providedGroups = new ArrayList<>();
+ providedGroups.add("virtual_group_2");
+
+ // This should not throw an exception since the combined groups include
+ // both virtual_group_1 (from subject) and virtual_group_2 (provided)
+ // which are both authorized to impersonate
+ gProvider.authorize(userGroupInformation, InetAddress.getByName("2.2.2.2"), providedGroups);
+ }
+ /**
+ * Test the isProxyGroupFound method directly.
+ * This method checks if any of the real user's groups have permission to impersonate the proxy user.
+ *
+ * @throws Exception if an error occurs during the test
+ */
+ @Test
+ public void testIsProxyGroupFound() throws Exception {
+ // Set up the provider with specific configuration
+ KnoxImpersonationProvider testProvider = new KnoxImpersonationProvider();
+ Configuration testConfig = new Configuration();
+
+ // Configure a specific proxy group
+ testConfig.set("hadoop.proxygroup.authorized_group.users", "*");
+ testConfig.set("hadoop.proxygroup.authorized_group.groups", "*");
+ testConfig.set("hadoop.proxygroup.authorized_group.hosts", "*");
+
+ testProvider.setConf(testConfig);
+ testProvider.init("hadoop.proxygroup");
+
+ // Create mocks for testing
+ UserGroupInformation userToImpersonate = Mockito.mock(UserGroupInformation.class);
+ when(userToImpersonate.getUserName()).thenReturn("impersonatedUser");
+
+ // Set up the method to test via reflection
+ java.lang.reflect.Method method = KnoxImpersonationProvider.class.getDeclaredMethod(
+ "isProxyGroupFound",
+ UserGroupInformation.class,
+ Set.class,
+ boolean.class);
+ method.setAccessible(true);
+
+ // Test case 1: User belongs to an authorized group
+ Set<String> authorizedGroups = new HashSet<>();
+ authorizedGroups.add("authorized_group");
+
+ boolean result1 = (boolean) method.invoke(testProvider, userToImpersonate, authorizedGroups, false);
+ assertTrue("User with authorized group should be allowed to impersonate", result1);
+
+ // Test case 2: User belongs to an unauthorized group
+ Set<String> unauthorizedGroups = new HashSet<>();
+ unauthorizedGroups.add("unauthorized_group");
+
+ boolean result2 = (boolean) method.invoke(testProvider, userToImpersonate, unauthorizedGroups, false);
+ assertFalse("User with unauthorized group should not be allowed to impersonate", result2);
+
+ // Test case 3: User belongs to multiple groups, including an authorized one
+ Set<String> mixedGroups = new HashSet<>();
+ mixedGroups.add("unauthorized_group");
+ mixedGroups.add("authorized_group");
+
+ boolean result3 = (boolean) method.invoke(testProvider, userToImpersonate, mixedGroups, false);
+ assertTrue("User with mixed groups including an authorized one should be allowed to impersonate", result3);
+
+ // Test case 4: Empty group set
+ Set<String> emptyGroups = new HashSet<>();
+
+ boolean result4 = (boolean) method.invoke(testProvider, userToImpersonate, emptyGroups, false);
+ assertFalse("User with no groups should not be allowed to impersonate", result4);
+
+ // Test case 5: Initial proxyGroupFound is true
+ boolean result5 = (boolean) method.invoke(testProvider, userToImpersonate, unauthorizedGroups, true);
+ assertTrue("Method should return true if initial proxyGroupFound is true", result5);
+ }
+ /**
+ * Test the authorize method when doesProxyUserConfigExist is false and groups exist.
+ * In this scenario, the method should directly call checkProxyGroupAuthorization.
+ *
+ * @throws Exception if an error occurs during the test
+ */
+ @Test
+ public void testAuthorizeWhenProxyUserConfigDoesNotExistAndGroupsExist() throws Exception {
+ // Set up a provider with only proxy group configuration (no proxy user configuration)
+ KnoxImpersonationProvider testProvider = new KnoxImpersonationProvider();
+ Configuration testConfig = new Configuration();
+
+ // Configure only proxy groups, not proxy users
+ testConfig.set("hadoop.proxygroup.authorized_group.users", "*");
+ testConfig.set("hadoop.proxygroup.authorized_group.groups", "*");
+ testConfig.set("hadoop.proxygroup.authorized_group.hosts", "*");
+
+ testProvider.setConf(testConfig);
+ testProvider.init("hadoop.proxygroup");
+
+ // Create mocks for testing
+ String proxyUser = "testuser";
+ String impersonatedUser = "impersonatedUser";
+
+ UserGroupInformation realUserUGI = Mockito.mock(UserGroupInformation.class);
+ UserGroupInformation userGroupInformation = Mockito.mock(UserGroupInformation.class);
+
+ when(realUserUGI.getUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getGroupNames()).thenReturn(new String[0]); // No groups in subject
+
+ when(userGroupInformation.getRealUser()).thenReturn(realUserUGI);
+ when(userGroupInformation.getUserName()).thenReturn(impersonatedUser);
+
+ // Create a list of groups to provide to the authorize method
+ List<String> providedGroups = new ArrayList<>();
+ providedGroups.add("authorized_group");
+
+ // This should not throw an exception since the provided groups include authorized_group
+ // which is authorized to impersonate
+ testProvider.authorize(userGroupInformation, InetAddress.getByName("2.2.2.2"), providedGroups);
+ }
+
+ /**
+ * Test the authorize method when doesProxyUserConfigExist is true and valid proxyuser configuration exists.
+ * In this scenario, the method should successfully authorize using the parent class's authorize method.
+ *
+ * @throws Exception if an error occurs during the test
+ */
+ @Test
+ public void testAuthorizeWhenProxyUserConfigExistsAndIsValid() throws Exception {
+ // Set up a provider with valid proxy user configuration
+ KnoxImpersonationProvider testProvider = new KnoxImpersonationProvider();
+ Configuration testConfig = new Configuration();
+
+ String proxyUser = "testuser";
+ String impersonatedUser = "impersonatedUser";
+
+ // Setup valid proxy user configuration
+ testConfig.set("hadoop.proxyuser." + proxyUser + ".users", "*");
+ testConfig.set("hadoop.proxyuser." + proxyUser + ".groups", "*");
+ testConfig.set("hadoop.proxyuser." + proxyUser + ".hosts", "*");
+
+ testProvider.setConf(testConfig);
+ testProvider.init("hadoop.proxyuser");
+
+ // Create mocks for testing
+ UserGroupInformation realUserUGI = Mockito.mock(UserGroupInformation.class);
+ UserGroupInformation userGroupInformation = Mockito.mock(UserGroupInformation.class);
+
+ when(realUserUGI.getShortUserName()).thenReturn(proxyUser);
+ when(realUserUGI.getUserName()).thenReturn(proxyUser);
+
+ when(userGroupInformation.getRealUser()).thenReturn(realUserUGI);
+ when(userGroupInformation.getUserName()).thenReturn(impersonatedUser);
+
+ // This should not throw an exception since the proxy user configuration is valid
+ testProvider.authorize(userGroupInformation, InetAddress.getByName("2.2.2.2"));
+ }
+
+ /**
+ * Test the authorize method when doesProxyUserConfigExist is true but the proxy user authorization fails,
+ * causing it to fall back to checkProxyGroupAuthorization.
+ *
+ * @throws Exception if an error occurs during the test
+ */
+ @Test
+ public void testAuthorizeWhenProxyUserConfigExistsButAuthorizationFails() throws Exception {
+ // Set up a provider with proxy user configuration that will fail authorization
+ // but with valid proxy group configuration
+ KnoxImpersonationProvider testProvider = new KnoxImpersonationProvider();
+ Configuration testConfig = new Configuration();
+
+ String proxyUser = "testuser";
+ String impersonatedUser = "impersonatedUser";
+ String unauthorizedUser = "unauthorizedUser"; // Different from the configured proxy user
+
+ // Setup proxy user configuration for a specific user
+ testConfig.set("hadoop.proxyuser." + proxyUser + ".users", "specificUser"); // Not wildcard
+ testConfig.set("hadoop.proxyuser." + proxyUser + ".groups", "specificGroup"); // Not wildcard
+ testConfig.set("hadoop.proxyuser." + proxyUser + ".hosts", "*");
+
+ // Setup proxy group configuration
+ testConfig.set("hadoop.proxygroup.authorized_group.users", "*");
+ testConfig.set("hadoop.proxygroup.authorized_group.groups", "*");
+ testConfig.set("hadoop.proxygroup.authorized_group.hosts", "*");
+
+ testProvider.setConf(testConfig);
+ testProvider.init("hadoop.proxyuser");
+
+ // Create mocks for testing
+ UserGroupInformation realUserUGI = Mockito.mock(UserGroupInformation.class);
+ UserGroupInformation userGroupInformation = Mockito.mock(UserGroupInformation.class);
+
+ when(realUserUGI.getShortUserName()).thenReturn(unauthorizedUser); // Use unauthorized user
+ when(realUserUGI.getUserName()).thenReturn(unauthorizedUser);
+ when(realUserUGI.getGroupNames()).thenReturn(new String[0]); // No groups in subject
+
+ when(userGroupInformation.getRealUser()).thenReturn(realUserUGI);
+ when(userGroupInformation.getUserName()).thenReturn(impersonatedUser);
+
+ // Create a list of groups to provide to the authorize method
+ List<String> providedGroups = new ArrayList<>();
+ providedGroups.add("authorized_group");
+
+ // This should not throw an exception because even though proxy user authorization fails,
+ // it falls back to group-based authorization which succeeds
+ testProvider.authorize(userGroupInformation, InetAddress.getByName("2.2.2.2"), providedGroups);
+ }
+}