zeroflag commented on code in PR #1043: URL: https://github.com/apache/knox/pull/1043#discussion_r2104470497
########## gateway-provider-identity-assertion-proxygroups/src/main/java/org/apache/knox/gateway/identityasserter/proxygroups/filter/ProxygroupIdentityAssertionFilter.java: ########## @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.identityasserter.proxygroups.filter; + +import org.apache.knox.gateway.i18n.messages.MessagesFactory; +import org.apache.knox.gateway.identityasserter.hadoop.groups.filter.HadoopGroupProviderFilter; +import org.apache.knox.gateway.security.SubjectUtils; +import org.apache.knox.gateway.services.GatewayServices; +import org.apache.knox.gateway.util.AuthFilterUtils; +import org.apache.knox.gateway.util.AuthorizationException; +import org.apache.knox.gateway.util.HttpExceptionUtils; + +import javax.security.auth.Subject; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; + +import static org.apache.knox.gateway.util.AuthFilterUtils.DEFAULT_IMPERSONATION_MODE; +import static org.apache.knox.gateway.util.AuthFilterUtils.IMPERSONATION_MODE; +import static org.apache.knox.gateway.util.AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION; + +public class ProxygroupIdentityAssertionFilter extends HadoopGroupProviderFilter { + private static final ProxygroupProviderMessages LOG = MessagesFactory.get(ProxygroupProviderMessages.class); + private String impersonationMode = DEFAULT_IMPERSONATION_MODE; + private EnumSet<AuthFilterUtils.ImpersonationFlags> impersonationFlags; + private String topologyName; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + super.init(filterConfig); + topologyName = (String) filterConfig.getServletContext().getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE); + impersonationFlags = AuthFilterUtils.getImpersonationEnabledFlags(filterConfig); + impersonationMode = filterConfig.getInitParameter(IMPERSONATION_MODE) != null ? filterConfig.getInitParameter(IMPERSONATION_MODE) : DEFAULT_IMPERSONATION_MODE; + + /* Add group impersonation provider */ + if (!impersonationFlags.isEmpty() && impersonationFlags.contains(GROUP_IMPERSONATION)) { Review Comment: Isn't this `isEmpty()` check redundant? ########## gateway-provider-identity-assertion-proxygroups/src/main/java/org/apache/knox/gateway/identityasserter/proxygroups/filter/ProxygroupIdentityAssertionFilter.java: ########## @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.identityasserter.proxygroups.filter; + +import org.apache.knox.gateway.i18n.messages.MessagesFactory; +import org.apache.knox.gateway.identityasserter.hadoop.groups.filter.HadoopGroupProviderFilter; +import org.apache.knox.gateway.security.SubjectUtils; +import org.apache.knox.gateway.services.GatewayServices; +import org.apache.knox.gateway.util.AuthFilterUtils; +import org.apache.knox.gateway.util.AuthorizationException; +import org.apache.knox.gateway.util.HttpExceptionUtils; + +import javax.security.auth.Subject; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; + +import static org.apache.knox.gateway.util.AuthFilterUtils.DEFAULT_IMPERSONATION_MODE; +import static org.apache.knox.gateway.util.AuthFilterUtils.IMPERSONATION_MODE; +import static org.apache.knox.gateway.util.AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION; + +public class ProxygroupIdentityAssertionFilter extends HadoopGroupProviderFilter { + private static final ProxygroupProviderMessages LOG = MessagesFactory.get(ProxygroupProviderMessages.class); + private String impersonationMode = DEFAULT_IMPERSONATION_MODE; + private EnumSet<AuthFilterUtils.ImpersonationFlags> impersonationFlags; + private String topologyName; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + super.init(filterConfig); + topologyName = (String) filterConfig.getServletContext().getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE); + impersonationFlags = AuthFilterUtils.getImpersonationEnabledFlags(filterConfig); + impersonationMode = filterConfig.getInitParameter(IMPERSONATION_MODE) != null ? filterConfig.getInitParameter(IMPERSONATION_MODE) : DEFAULT_IMPERSONATION_MODE; + + /* Add group impersonation provider */ + if (!impersonationFlags.isEmpty() && impersonationFlags.contains(GROUP_IMPERSONATION)) { + final List<String> initParameterNames = AuthFilterUtils.getInitParameterNamesAsList(filterConfig); + String topologyName = (String) filterConfig.getServletContext().getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE); + AuthFilterUtils.refreshProxyGroupsConfiguration(filterConfig, initParameterNames, topologyName, ProxygroupsIdentityAsserterDeploymentContributor.NAME); + } + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + final Subject subject = getSubject(); + /* + * in case of user auth failure, mapped principal is same as real principal, this is so that + * group lookup works needed for group proxy auth check + */ + String mappedPrincipalName = SubjectUtils.getEffectivePrincipalName(subject); + + boolean isProxyUserAuthorized = false; + boolean isProxyGroupAuthorized = false; + + /* Save exceptions if needed */ + AuthorizationException userAuthorizationException = null; + AuthorizationException groupsAuthorizationException = null; + + if (impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.USER_IMPERSONATION)) { + try { + mappedPrincipalName = handleProxyUserImpersonation(request, subject); + isProxyUserAuthorized = true; + } catch (AuthorizationException e) { + LOG.hadoopAuthProxyUserFailed(e); + userAuthorizationException = e; + } + } + + mappedPrincipalName = getMappedPrincipalName(request, mappedPrincipalName, subject); + String[] groups = getMappedGroups(request, mappedPrincipalName, subject); + + if (impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION)) { + try { + mappedPrincipalName = handleProxyGroupImpersonation(request, subject, groups); + isProxyGroupAuthorized = true; + } catch (AuthorizationException e) { + LOG.hadoopAuthProxyGroupFailed(e); + groupsAuthorizationException = e; + } + } + + // Determine if authorization succeeded based on the impersonation mode + /* + * Impersonation mode can only come into play when both proxy user and proxy group are enabled. + * For cases where only one of them is enabled, we set impersonation mode to OR and rely on either + * of the modes to succeed. + * + * This is the case where one of the impersonations is disabled but hadoop.impersonation.mode value is + * configured. + */ + if (impersonationFlags.isEmpty() || (!impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.USER_IMPERSONATION) || !impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION))) { + impersonationMode = DEFAULT_IMPERSONATION_MODE; + } + + boolean isAuthorized = "AND".equalsIgnoreCase(impersonationMode) + ? (isProxyUserAuthorized && isProxyGroupAuthorized) + : (isProxyUserAuthorized || isProxyGroupAuthorized); + + if (isAuthorized) { + LOG.hadoopAuthProxyAccessSuccess(SubjectUtils.getEffectivePrincipalName(subject), request.getParameter(AuthFilterUtils.QUERY_PARAMETER_DOAS), impersonationMode); + } else { + // Handle authorization failures + if (groupsAuthorizationException != null) { + LOG.hadoopAuthProxyGroupFailed(groupsAuthorizationException); + HttpExceptionUtils.createServletExceptionResponse((HttpServletResponse) response, HttpServletResponse.SC_FORBIDDEN, groupsAuthorizationException); + return; + } else if (userAuthorizationException != null) { + LOG.hadoopAuthProxyGroupFailed(groupsAuthorizationException); + HttpExceptionUtils.createServletExceptionResponse((HttpServletResponse) response, HttpServletResponse.SC_FORBIDDEN, userAuthorizationException); + return; + } else { + AuthorizationException e = new AuthorizationException("User: " + SubjectUtils.getEffectivePrincipalName(subject) + " is not allowed to impersonate." + "Proxyuser auth result: " + isProxyUserAuthorized + " Proxygroup auth result: " + isProxyGroupAuthorized); + LOG.hadoopAuthProxyGroupFailed(e); + HttpExceptionUtils.createServletExceptionResponse((HttpServletResponse) response, HttpServletResponse.SC_FORBIDDEN, e); + return; + } + } + + HttpServletRequestWrapper wrapper = wrapHttpServletRequest(request, mappedPrincipalName); + continueChainAsPrincipal(wrapper, response, chain, mappedPrincipalName, unique(groups)); + } + + private String handleProxyGroupImpersonation(final ServletRequest request, final Subject subject, String[] groups) throws AuthorizationException { + String principalName = SubjectUtils.getEffectivePrincipalName(subject); + if (!impersonationFlags.isEmpty() && impersonationFlags.contains(GROUP_IMPERSONATION)) { Review Comment: Isn't this `isEmpty()` check redundant? ########## gateway-spi/src/main/java/org/apache/knox/gateway/util/GroupBasedImpersonationProvider.java: ########## @@ -0,0 +1,228 @@ +/* + * 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.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * 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 GroupBasedImpersonationProvider 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 final Map<String, AccessControlList> proxyGroupsAcls = new HashMap<>(); + EnumSet<AuthFilterUtils.ImpersonationFlags> impersonationFlags = EnumSet.noneOf(AuthFilterUtils.ImpersonationFlags.class); + private Map<String, MachineList> groupProxyHosts = new HashMap<>(); + private String groupConfigPrefix; + + public GroupBasedImpersonationProvider(EnumSet<AuthFilterUtils.ImpersonationFlags> impersonationFlags) { + super(); + this.impersonationFlags = impersonationFlags; + } + + @Override + public Configuration getConf() { + return super.getConf(); + } + + @Override + public void setConf(Configuration conf) { + super.setConf(conf); + } + + @Override + public void init(String proxyGroupPrefix) { + initGroupBasedProvider(proxyGroupPrefix); + } + + private void initGroupBasedProvider(String proxyGroupPrefix) { + groupConfigPrefix = proxyGroupPrefix + + (proxyGroupPrefix.endsWith(".") ? "" : "."); + + // constructing regex to match the following patterns: + // $configPrefix.[ANY].users + // $configPrefix.[ANY].groups + // $configPrefix.[ANY].hosts + // + String prefixRegEx = groupConfigPrefix.replace(".", "\\."); + String usersGroupsRegEx = prefixRegEx + "[\\S]*(" + + Pattern.quote(CONF_USERS) + "|" + Pattern.quote(CONF_GROUPS) + ")"; + String hostsRegEx = prefixRegEx + "[\\S]*" + Pattern.quote(CONF_HOSTS); + + // 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 groups that are already in Subject + * + * @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 { + // If both authorization methods are disabled, allow the operation to proceed + if (impersonationFlags.isEmpty() || !impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION)) { + LOG.successfulImpersonation(user.getRealUser().getUserName(), user.getUserName()); + return; + } + checkProxyGroupAuthorization(user, remoteAddress, Collections.emptyList()); + } + + /** + * Authorization based on groups that are provided as a function agument + * + * @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 { + // If both authorization methods are disabled, allow the operation to proceed + if (impersonationFlags.isEmpty() || !impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION)) { Review Comment: Isn't this `isEmpty()` check redundant? ########## gateway-spi/src/main/java/org/apache/knox/gateway/util/GroupBasedImpersonationProvider.java: ########## @@ -0,0 +1,228 @@ +/* + * 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.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * 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 GroupBasedImpersonationProvider 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 final Map<String, AccessControlList> proxyGroupsAcls = new HashMap<>(); + EnumSet<AuthFilterUtils.ImpersonationFlags> impersonationFlags = EnumSet.noneOf(AuthFilterUtils.ImpersonationFlags.class); + private Map<String, MachineList> groupProxyHosts = new HashMap<>(); + private String groupConfigPrefix; + + public GroupBasedImpersonationProvider(EnumSet<AuthFilterUtils.ImpersonationFlags> impersonationFlags) { + super(); + this.impersonationFlags = impersonationFlags; + } + + @Override + public Configuration getConf() { + return super.getConf(); + } + + @Override + public void setConf(Configuration conf) { + super.setConf(conf); + } + + @Override + public void init(String proxyGroupPrefix) { + initGroupBasedProvider(proxyGroupPrefix); + } + + private void initGroupBasedProvider(String proxyGroupPrefix) { + groupConfigPrefix = proxyGroupPrefix + + (proxyGroupPrefix.endsWith(".") ? "" : "."); + + // constructing regex to match the following patterns: + // $configPrefix.[ANY].users + // $configPrefix.[ANY].groups + // $configPrefix.[ANY].hosts + // + String prefixRegEx = groupConfigPrefix.replace(".", "\\."); + String usersGroupsRegEx = prefixRegEx + "[\\S]*(" + + Pattern.quote(CONF_USERS) + "|" + Pattern.quote(CONF_GROUPS) + ")"; + String hostsRegEx = prefixRegEx + "[\\S]*" + Pattern.quote(CONF_HOSTS); + + // 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 groups that are already in Subject + * + * @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 { + // If both authorization methods are disabled, allow the operation to proceed + if (impersonationFlags.isEmpty() || !impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION)) { Review Comment: Isn't this `isEmpty()` check redundant? ########## gateway-spi/src/main/java/org/apache/knox/gateway/util/GroupBasedImpersonationProvider.java: ########## @@ -0,0 +1,228 @@ +/* + * 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.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * 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 GroupBasedImpersonationProvider 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 final Map<String, AccessControlList> proxyGroupsAcls = new HashMap<>(); + EnumSet<AuthFilterUtils.ImpersonationFlags> impersonationFlags = EnumSet.noneOf(AuthFilterUtils.ImpersonationFlags.class); + private Map<String, MachineList> groupProxyHosts = new HashMap<>(); + private String groupConfigPrefix; + + public GroupBasedImpersonationProvider(EnumSet<AuthFilterUtils.ImpersonationFlags> impersonationFlags) { + super(); + this.impersonationFlags = impersonationFlags; + } + + @Override + public Configuration getConf() { + return super.getConf(); + } + + @Override + public void setConf(Configuration conf) { + super.setConf(conf); + } + + @Override + public void init(String proxyGroupPrefix) { + initGroupBasedProvider(proxyGroupPrefix); + } + + private void initGroupBasedProvider(String proxyGroupPrefix) { + groupConfigPrefix = proxyGroupPrefix + + (proxyGroupPrefix.endsWith(".") ? "" : "."); + + // constructing regex to match the following patterns: + // $configPrefix.[ANY].users + // $configPrefix.[ANY].groups + // $configPrefix.[ANY].hosts + // + String prefixRegEx = groupConfigPrefix.replace(".", "\\."); + String usersGroupsRegEx = prefixRegEx + "[\\S]*(" + + Pattern.quote(CONF_USERS) + "|" + Pattern.quote(CONF_GROUPS) + ")"; + String hostsRegEx = prefixRegEx + "[\\S]*" + Pattern.quote(CONF_HOSTS); + + // 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 groups that are already in Subject + * + * @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 { + // If both authorization methods are disabled, allow the operation to proceed + if (impersonationFlags.isEmpty() || !impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION)) { + LOG.successfulImpersonation(user.getRealUser().getUserName(), user.getUserName()); + return; + } + checkProxyGroupAuthorization(user, remoteAddress, Collections.emptyList()); + } + + /** + * Authorization based on groups that are provided as a function agument + * + * @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 { + // If both authorization methods are disabled, allow the operation to proceed + if (impersonationFlags.isEmpty() || !impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION)) { + LOG.successfulImpersonation(user.getRealUser().getUserName(), user.getUserName()); + return; + } + checkProxyGroupAuthorization(user, remoteAddress, groups); + } + + /** + * 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 + for (String group : realUserGroups) { + final AccessControlList acl = proxyGroupsAcls.get(groupConfigPrefix + + group); + + if (acl == null || !acl.isUserAllowed(user)) { + continue; + } else { + proxyGroupFound = true; + break; + } + } + + 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; + 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; + } + } + + if (!proxyGroupHostFound) { + LOG.failedToImpersonateGroupsFromAddress(realUser.getUserName(), realUserGroups.toString(), user.getUserName(), remoteAddress.toString()); + throw new AuthorizationException("User: " + realUser.getUserName() + + "with groups " + realUserGroups.toString() Review Comment: Missing space before `"with groups` ########## gateway-spi/src/main/java/org/apache/knox/gateway/util/AuthFilterUtils.java: ########## @@ -42,212 +45,307 @@ 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"; - - 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); - }); - } - - 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(); - } - - @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; - } - - 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); - } - } - - private static void authorizeImpersonationRequest(HttpServletRequest request, UserGroupInformation remoteRequestUgi, String topologyName, String role) - throws AuthorizationException { - - 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!"); - } - } - - 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)); - } + 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 PROXYGROUP_PREFIX = "hadoop.proxygroup"; + public static final String IMPERSONATION_MODE = "hadoop.impersonation.mode"; + public static final String DEFAULT_IMPERSONATION_MODE = "OR"; + public static final String IMPERSONATION_ENABLED_PARAM = AuthFilterUtils.PROXYUSER_PREFIX + ".impersonation.enabled"; + public static final String GROUP_IMPERSONATION_ENABLED_PARAM = AuthFilterUtils.PROXYGROUP_PREFIX + ".impersonation.enabled"; + + 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(); + + /** + * Represents the modes of impersonation that can be configured and used + * within the authentication process. + * + * USER_IMPERSONATION: + * Indicates that the impersonation process is based on a user identity. + * This is typically used when one user needs to act on behalf of another. + * + * GROUP_IMPERSONATION: + * Represents group-based impersonation where actions can be performed + * based on group roles or permissions. + */ + public enum ImpersonationFlags { + USER_IMPERSONATION, GROUP_IMPERSONATION + } + + + /** + * 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); + }); + } + + saveImpersonationProvider(topologyName, role, conf, new DefaultImpersonationProvider(), PROXYUSER_PREFIX); + } + + /* For proxy groups */ + public static void refreshProxyGroupsConfiguration(FilterConfig filterConfig, List<String> initParameterNames, String topologyName, String role) { + if (filterConfig == null) { + throw new IllegalArgumentException("Cannot get proxyuser configuration from NULL filter config"); + } + refreshProxyGroupsConfiguration(null, filterConfig, initParameterNames, topologyName, role); + } + + private static void refreshProxyGroupsConfiguration(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(PROXYGROUP_PREFIX + ".")).forEach(name -> { + String value = context == null ? filterConfig.getInitParameter(name) : context.getInitParameter(name); + conf.set(name, value); + }); + initParameterNames.stream().filter(name -> name.startsWith(IMPERSONATION_MODE + ".")).forEach(name -> { + String value = context == null ? filterConfig.getInitParameter(name) : context.getInitParameter(name); + conf.set(name, value); + }); + } + /* For proxy group use GroupBasedImpersonationProvider */ + saveImpersonationProvider(topologyName, role, conf, new GroupBasedImpersonationProvider(getImpersonationEnabledFlags(filterConfig)), PROXYGROUP_PREFIX); + } + + + 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(); + } + } + + 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(); + } + + @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; + } + + public static void authorizeGroupImpersonationRequest(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); + } + } + + 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); + } + } + + private static void authorizeImpersonationRequest(HttpServletRequest request, UserGroupInformation remoteRequestUgi, String topologyName, String role) + throws AuthorizationException { + authorizeImpersonationRequest(request, remoteRequestUgi, topologyName, role, Collections.emptyList()); + } + + 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 GroupBasedImpersonationProvider) { + ((GroupBasedImpersonationProvider) 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)); + } + + /** + * Check if user or group impersonation is enabled based on filter configuration. + * + * @param filterConfig The filter configuration + * @return A boolean array where the first element indicates if user impersonation is enabled Review Comment: Stale javadoc comment. It no longer returns a boolean array. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: dev-unsubscr...@knox.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org