diff options
Diffstat (limited to 'exec/java-exec/src/main/java/org/apache/drill/exec/resourcemgr/config/selectors/AclSelector.java')
-rw-r--r-- | exec/java-exec/src/main/java/org/apache/drill/exec/resourcemgr/config/selectors/AclSelector.java | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/resourcemgr/config/selectors/AclSelector.java b/exec/java-exec/src/main/java/org/apache/drill/exec/resourcemgr/config/selectors/AclSelector.java new file mode 100644 index 000000000..50acb860d --- /dev/null +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/resourcemgr/config/selectors/AclSelector.java @@ -0,0 +1,285 @@ +/* + * 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 avro.shaded.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(); + } +} |