blob: 304eae54140516e011a743ce4d0510c6f3ea0752 [file] [log] [blame]
/*
* 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 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 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();
}
}