aboutsummaryrefslogtreecommitdiff
path: root/exec/java-exec/src/main/java/org/apache/drill/exec/resourcemgr/config/selectors/AclSelector.java
diff options
context:
space:
mode:
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.java285
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();
+ }
+}