| /* |
| * 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.drill.exec.resourcemgr.config.selectors; |
| |
| import org.apache.drill.shaded.guava.com.google.common.annotations.VisibleForTesting; |
| import com.typesafe.config.Config; |
| import org.apache.drill.exec.ops.QueryContext; |
| import org.apache.drill.exec.resourcemgr.config.exception.RMConfigException; |
| import org.apache.drill.exec.util.ImpersonationUtil; |
| import org.apache.drill.shaded.guava.com.google.common.collect.Sets; |
| import org.apache.hadoop.security.UserGroupInformation; |
| |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Evaluates if a query can be admitted to a ResourcePool or not by comparing query user/groups with the |
| * configured users/groups policies for this selector. AclSelector can be configured using both long-form syntax or |
| * short-form syntax as defined below: |
| * <ul> |
| * <li>Long-Form Syntax: Allows to use identifiers to specify allowed and disallowed users/groups. For example: |
| * users: [alice:+, bob:-] means alice is allowed whereas bob is denied access to the pool</li> |
| * <li>Short-Form Syntax: Allows to specify lists of allowed users/groups only. For example: users: [alice, bob] |
| * means only alice and bob are allowed access to this pool</li> |
| * </ul> |
| * The selector also supports * as a wildcard for both long and short form syntax to allow/deny all users/groups. |
| * Example configuration is of form: |
| * <code><pre> |
| * selector: { |
| * acl: { |
| * users: [alice:+, bob:-], |
| * groups: [sales, marketing] |
| * } |
| * } |
| * </pre></code> |
| */ |
| public class AclSelector extends AbstractResourcePoolSelector { |
| private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(AclSelector.class); |
| |
| private final Set<String> allowedUsers = Sets.newHashSet(); |
| |
| private final Set<String> allowedGroups = Sets.newHashSet(); |
| |
| private final Set<String> deniedUsers = Sets.newHashSet(); |
| |
| private final Set<String> deniedGroups = Sets.newHashSet(); |
| |
| private final Config aclSelectorValue; |
| |
| private static final String ACL_VALUE_GROUPS_KEY = "groups"; |
| |
| private static final String ACL_VALUE_USERS_KEY = "users"; |
| |
| private static final String ACL_LONG_SYNTAX_SEPARATOR = ":"; |
| |
| private static final String ACL_LONG_ALLOWED_IDENTIFIER = "+"; |
| |
| private static final String ACL_LONG_DISALLOWED_IDENTIFIER = "-"; |
| |
| private static final String ACL_ALLOW_ALL = "*"; |
| |
| |
| AclSelector(Config configValue) throws RMConfigException { |
| super(SelectorType.ACL); |
| this.aclSelectorValue = configValue; |
| validateAndParseACL(aclSelectorValue); |
| } |
| |
| /** |
| * Determines if a given query is selected by this ACL selector of a Resource Pool or not. Following rules are |
| * followed to evaluate the selection. Assumption: There is an assumption made that if a user or group is configured |
| * in both +ve/-ve respective lists then it will be treated to be present in -ve list. |
| * |
| * Rules: |
| * 1) Check if query user is present in -ve users list, If yes then query is not selected else go to 2 |
| * 2) Check if query user is present in +ve users list, If yes then query is selected else go to 3 |
| * 3) Check if * is present in -ve users list, if yes then query is not selected else go to 4 |
| * 4) Check if * is present in +ve users list, if yes then query is selected else go to 5 |
| * 5) If here that means query user or * is absent in both +ve and -ve users list so check for groups of query user |
| * in step 6 |
| * 6) Check if any of groups of query user is present in -ve groups list, If yes then query is not selected else go |
| * to 7 |
| * 7) Check if any of groups of query user is present in +ve groups list, If yes then query selected else go to 8 |
| * 8) Check if * is present in -ve groups list, If yes then query is not selected else go to 9 |
| * 9) Check if * is present in +ve groups list, If yes then query is selected else go to 10 |
| * 10) Query user and groups of it is neither present is +ve/-ve users list not +ve/-ve groups list hence the query |
| * is not selected |
| * |
| * @param queryContext QueryContext to get information about query user |
| * @return true if a query is selected by this selector, false otherwise |
| */ |
| @Override |
| public boolean isQuerySelected(QueryContext queryContext) { |
| final String queryUser = queryContext.getQueryUserName(); |
| final UserGroupInformation queryUserUGI = ImpersonationUtil.createProxyUgi(queryUser); |
| final Set<String> queryGroups = Sets.newHashSet(queryUserUGI.getGroupNames()); |
| return checkQueryUserGroups(queryUser, queryGroups); |
| } |
| |
| @VisibleForTesting |
| public boolean checkQueryUserGroups(String queryUser, Set<String> queryGroups) { |
| // Check for +ve/-ve users information with query user |
| if (deniedUsers.contains(queryUser)) { |
| logger.debug("Query user is present in configured ACL -ve users list"); |
| return false; |
| } else if (allowedUsers.contains(queryUser)) { |
| logger.debug("Query user is present in configured ACL +ve users list"); |
| return true; |
| } else if (isStarInDisAllowedUsersList()) { |
| logger.debug("Query user is absent in configured ACL +ve/-ve users list but * is in -ve users list"); |
| return false; |
| } else if (isStarInAllowedUsersList()) { |
| logger.debug("Query user is absent in configured ACL +ve/-ve users list but * is in +ve users list"); |
| return true; |
| } |
| |
| // Check for +ve/-ve groups information with groups of query user |
| if (Sets.intersection(queryGroups, deniedGroups).size() > 0) { |
| logger.debug("Groups of Query user is present in configured ACL -ve groups list"); |
| return false; |
| } else if (Sets.intersection(queryGroups, allowedGroups).size() > 0) { |
| logger.debug("Groups of Query user is present in configured ACL +ve groups list"); |
| return true; |
| } else if (isStarInDisAllowedGroupsList()) { |
| logger.debug("Groups of Query user is absent in configured ACL +ve/-ve groups list but * is in -ve groups list"); |
| return false; |
| } else if (isStarInAllowedGroupsList()) { |
| logger.debug("Groups of Query user is absent in configured ACL +ve/-ve groups list but * is in +ve groups list"); |
| return true; |
| } |
| |
| logger.debug("Neither query user or group is present in configured ACL users/groups list"); |
| return false; |
| } |
| |
| /** |
| * Parses the acl selector config value for users and groups to populate list of allowed/denied users and groups. |
| * @param aclConfig Acl config to parse |
| * @throws RMConfigException in case of invalid config for either users/groups |
| */ |
| private void validateAndParseACL(Config aclConfig) throws RMConfigException { |
| |
| // ACL config doesn't have either group or user list |
| if (!aclConfig.hasPath(ACL_VALUE_GROUPS_KEY) && !aclConfig.hasPath(ACL_VALUE_USERS_KEY)) { |
| throw new RMConfigException(String.format("ACL Selector config is missing both group and user list information." + |
| " Please configure either of groups or users list. [Details: aclConfig: %s]", aclConfig)); |
| } |
| |
| if (aclConfig.hasPath(ACL_VALUE_USERS_KEY)) { |
| final List<String> users = aclSelectorValue.getStringList(ACL_VALUE_USERS_KEY); |
| parseACLInput(users, allowedUsers, deniedUsers); |
| } |
| |
| if (aclConfig.hasPath(ACL_VALUE_GROUPS_KEY)) { |
| final List<String> groups = aclSelectorValue.getStringList(ACL_VALUE_GROUPS_KEY); |
| parseACLInput(groups, allowedGroups, deniedGroups); |
| } |
| |
| // If no valid configuration is seen for this selector |
| if (allowedGroups.size() == 0 && deniedGroups.size() == 0 && |
| deniedUsers.size() == 0 && allowedUsers.size() == 0) { |
| throw new RMConfigException("No valid users or groups information is configured for this ACL selector. Either " + |
| "use * or valid users/groups"); |
| } |
| |
| // Check if there is any intersection between allowed and disallowed users/groups |
| Set<String> wrongConfig = Sets.intersection(allowedUsers, deniedUsers); |
| if (wrongConfig.size() > 0) { |
| logger.warn("These users are configured both in allowed and disallowed list. They will be treated as disallowed" + |
| ". [Details: users: {}]", wrongConfig); |
| allowedUsers.removeAll(wrongConfig); |
| } |
| |
| wrongConfig = Sets.intersection(allowedGroups, deniedGroups); |
| if (wrongConfig.size() > 0) { |
| logger.warn("These groups are configured both in allowed and disallowed list. They will be treated as " + |
| "disallowed. [Details: groups: {}]", wrongConfig); |
| allowedGroups.removeAll(wrongConfig); |
| } |
| } |
| |
| public Set<String> getAllowedUsers() { |
| return allowedUsers; |
| } |
| |
| public Set<String> getAllowedGroups() { |
| return allowedGroups; |
| } |
| |
| public Set<String> getDeniedUsers() { |
| return deniedUsers; |
| } |
| |
| public Set<String> getDeniedGroups() { |
| return deniedGroups; |
| } |
| |
| private boolean isStarInAllowedUsersList() { |
| return allowedUsers.contains(ACL_ALLOW_ALL); |
| } |
| |
| private boolean isStarInAllowedGroupsList() { |
| return allowedGroups.contains(ACL_ALLOW_ALL); |
| } |
| |
| private boolean isStarInDisAllowedUsersList() { |
| return deniedUsers.contains(ACL_ALLOW_ALL); |
| } |
| |
| private boolean isStarInDisAllowedGroupsList() { |
| return deniedGroups.contains(ACL_ALLOW_ALL); |
| } |
| |
| private void parseACLInput(List<String> acls, Set<String> allowedIdentity, Set<String> disAllowedIdentity) { |
| for (String aclValue : acls) { |
| |
| if (aclValue.isEmpty()) { |
| continue; |
| } |
| // Check if it's long form syntax or shortForm syntax |
| String[] aclValueSplits = aclValue.split(ACL_LONG_SYNTAX_SEPARATOR); |
| if (aclValueSplits.length == 1) { |
| // short form |
| if (!allowedIdentity.add(aclValueSplits[0])) { |
| logger.info("Duplicate acl identity: {} found in configured list will be ignored", aclValueSplits[0]); |
| } |
| } else { |
| // long form |
| final String identifier = aclValueSplits[1]; |
| if (identifier.equals(ACL_LONG_ALLOWED_IDENTIFIER)) { |
| if (!allowedIdentity.add(aclValueSplits[0])) { |
| logger.info("Duplicate acl identity: {} found in configured list will be ignored", aclValueSplits[0]); |
| } |
| } else if (identifier.equals(ACL_LONG_DISALLOWED_IDENTIFIER)) { |
| if (!disAllowedIdentity.add(aclValueSplits[0])) { |
| logger.info("Duplicate acl identity: {} found in configured list will be ignored", aclValueSplits[0]); |
| } |
| } else { |
| logger.error("Invalid long form syntax encountered hence ignoring ACL string {} . Details[Allowed " + |
| "identifiers are `+` and `-`. Encountered: {}]", aclValue, identifier); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("{ SelectorType: ").append(super.toString()); |
| sb.append(", AllowedUsers: ["); |
| for (String positiveUser : allowedUsers) { |
| sb.append(positiveUser).append(", "); |
| } |
| sb.append("], AllowedGroups: ["); |
| for (String positiveGroup : allowedGroups) { |
| sb.append(positiveGroup).append(", "); |
| } |
| sb.append("], DisallowedUsers: ["); |
| for (String negativeUser : deniedUsers) { |
| sb.append(negativeUser).append(", "); |
| } |
| sb.append("], DisallowedGroups: ["); |
| for (String negativeGroup : deniedGroups) { |
| sb.append(negativeGroup).append(", "); |
| } |
| sb.append("]}"); |
| |
| return sb.toString(); |
| } |
| } |