/*
* 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.ranger.plugin.policyevaluator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ranger.plugin.model.RangerPolicy;
import org.apache.ranger.plugin.model.RangerServiceDef;
import org.apache.ranger.plugin.policyengine.RangerAccessRequest;
import org.apache.ranger.plugin.policyengine.RangerAccessResource;
import org.apache.ranger.plugin.policyengine.RangerPolicyEngine;
import org.apache.ranger.plugin.policyengine.RangerPolicyEngineOptions;
import java.util.*;
public class RangerOptimizedPolicyEvaluator extends RangerDefaultPolicyEvaluator {
private static final Log LOG = LogFactory.getLog(RangerOptimizedPolicyEvaluator.class);
private Set<String> groups = new HashSet<>();
private Set<String> users = new HashSet<>();
private Set<String> accessPerms = new HashSet<>();
private boolean delegateAdmin;
private boolean hasAllPerms;
private boolean hasPublicGroup;
private boolean hasCurrentUser;
private boolean hasResourceOwner;
// For computation of priority
private static final String RANGER_POLICY_EVAL_MATCH_ANY_PATTERN_STRING = "*";
private static final String RANGER_POLICY_EVAL_MATCH_ONE_CHARACTER_STRING = "?";
private static final int RANGER_POLICY_EVAL_SCORE_DEFAULT = 10000;
private static final int RANGER_POLICY_EVAL_SCORE_MAX_DISCOUNT_RESOURCE = 100;
private static final int RANGER_POLICY_EVAL_SCORE_MAX_DISCOUNT_USERSGROUPS = 25;
private static final int RANGER_POLICY_EVAL_SCORE_MAX_DISCOUNT_ACCESS_TYPES = 25;
private static final int RANGER_POLICY_EVAL_SCORE_MAX_DISCOUNT_CUSTOM_CONDITIONS = 25;
private static final int RANGER_POLICY_EVAL_SCORE_RESOURCE_DISCOUNT_MATCH_ANY_WILDCARD = 25;
private static final int RANGER_POLICY_EVAL_SCORE_RESOURCE_DISCOUNT_HAS_MATCH_ANY_WILDCARD = 10;
private static final int RANGER_POLICY_EVAL_SCORE_RESOURCE_DISCOUNT_HAS_MATCH_ONE_CHARACTER_WILDCARD = 5;
private static final int RANGER_POLICY_EVAL_SCORE_RESOURCE_DISCOUNT_IS_EXCLUDES = 5;
private static final int RANGER_POLICY_EVAL_SCORE_RESORUCE_DISCOUNT_IS_RECURSIVE = 5;
private static final int RANGER_POLICY_EVAL_SCORE_CUSTOM_CONDITION_PENALTY = 5;
private static final int RANGER_POLICY_EVAL_SCORE_DYNAMIC_RESOURCE_EVAL_PENALTY = 20;
@Override
public void init(RangerPolicy policy, RangerServiceDef serviceDef, RangerPolicyEngineOptions options) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerOptimizedPolicyEvaluator.init()");
}
super.init(policy, serviceDef, options);
preprocessPolicyItems(policy.getPolicyItems());
preprocessPolicyItems(policy.getDenyPolicyItems());
preprocessPolicyItems(policy.getAllowExceptions());
preprocessPolicyItems(policy.getDenyExceptions());
preprocessPolicyItems(policy.getDataMaskPolicyItems());
preprocessPolicyItems(policy.getRowFilterPolicyItems());
hasAllPerms = checkIfHasAllPerms();
for (String user : users) {
if (!hasCurrentUser && RangerPolicyEngine.USER_CURRENT.equalsIgnoreCase(user)) {
hasCurrentUser = true;
}
if (!hasResourceOwner && RangerPolicyEngine.RESOURCE_OWNER.equalsIgnoreCase(user)) {
hasResourceOwner = true;
}
if (hasCurrentUser && hasResourceOwner) {
break;
}
}
for (String group : groups) {
if (RangerPolicyEngine.GROUP_PUBLIC.equalsIgnoreCase(group)) {
hasPublicGroup = true;
break;
}
}
setEvalOrder(computeEvalOrder());
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerOptimizedPolicyEvaluator.init()");
}
}
static class LevelResourceNames implements Comparable<LevelResourceNames> {
final int level;
final RangerPolicy.RangerPolicyResource policyResource;
public LevelResourceNames(int level, RangerPolicy.RangerPolicyResource policyResource) {
this.level = level;
this.policyResource = policyResource;
}
@Override
public int compareTo(LevelResourceNames other) {
// Sort in ascending order of level numbers
return Integer.compare(this.level, other.level);
}
@Override
public boolean equals(Object other) {
boolean ret = false;
if (other != null && (other instanceof LevelResourceNames)) {
ret = this == other || compareTo((LevelResourceNames) other) == 0;
}
return ret;
}
@Override
public int hashCode() {
return Objects.hashCode(this.level);
}
}
public int computeEvalOrder() {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerOptimizedPolicyEvaluator.computeEvalOrder()");
}
int evalOrder = RANGER_POLICY_EVAL_SCORE_DEFAULT;
RangerServiceDef serviceDef = getServiceDef();
List<RangerServiceDef.RangerResourceDef> resourceDefs = serviceDef.getResources();
RangerPolicy policy = getPolicy();
List<LevelResourceNames> tmpList = new ArrayList<>();
for (Map.Entry<String, RangerPolicy.RangerPolicyResource> kv : policy.getResources().entrySet()) {
String resourceName = kv.getKey();
RangerPolicy.RangerPolicyResource policyResource = kv.getValue();
List<String> resourceValues = policyResource.getValues();
if(CollectionUtils.isNotEmpty(resourceValues)) {
for (RangerServiceDef.RangerResourceDef resourceDef : resourceDefs) {
if (resourceName.equals(resourceDef.getName())) {
tmpList.add(new LevelResourceNames(resourceDef.getLevel(), policyResource));
break;
}
}
}
}
Collections.sort(tmpList); // Sort in ascending order of levels
int resourceDiscount = 0;
for (LevelResourceNames item : tmpList) {
// Expect lowest level first
boolean foundStarWildcard = false;
boolean foundQuestionWildcard = false;
boolean foundMatchAny = false;
for (String resourceName : item.policyResource.getValues()) {
if (resourceName.isEmpty() || RANGER_POLICY_EVAL_MATCH_ANY_PATTERN_STRING.equals(resourceName)) {
foundMatchAny = true;
break;
} else if (resourceName.contains(RANGER_POLICY_EVAL_MATCH_ANY_PATTERN_STRING)) {
foundStarWildcard = true;
} else if (resourceName.contains(RANGER_POLICY_EVAL_MATCH_ONE_CHARACTER_STRING)) {
foundQuestionWildcard = true;
}
}
if (foundMatchAny) {
resourceDiscount += RANGER_POLICY_EVAL_SCORE_RESOURCE_DISCOUNT_MATCH_ANY_WILDCARD;
} else {
if (foundStarWildcard) {
resourceDiscount += RANGER_POLICY_EVAL_SCORE_RESOURCE_DISCOUNT_HAS_MATCH_ANY_WILDCARD;
} else if (foundQuestionWildcard) {
resourceDiscount += RANGER_POLICY_EVAL_SCORE_RESOURCE_DISCOUNT_HAS_MATCH_ONE_CHARACTER_WILDCARD;
}
RangerPolicy.RangerPolicyResource resource = item.policyResource;
if (resource.getIsExcludes()) {
resourceDiscount += RANGER_POLICY_EVAL_SCORE_RESOURCE_DISCOUNT_IS_EXCLUDES;
}
if (resource.getIsRecursive()) {
resourceDiscount += RANGER_POLICY_EVAL_SCORE_RESORUCE_DISCOUNT_IS_RECURSIVE;
}
}
}
if (needsDynamicEval()) {
evalOrder += RANGER_POLICY_EVAL_SCORE_DYNAMIC_RESOURCE_EVAL_PENALTY;
}
evalOrder -= Math.min(RANGER_POLICY_EVAL_SCORE_MAX_DISCOUNT_RESOURCE, resourceDiscount);
if (hasPublicGroup || hasCurrentUser) {
evalOrder -= RANGER_POLICY_EVAL_SCORE_MAX_DISCOUNT_USERSGROUPS;
} else {
evalOrder -= Math.min(groups.size() + users.size(), RANGER_POLICY_EVAL_SCORE_MAX_DISCOUNT_USERSGROUPS);
}
evalOrder -= Math.round(((float)RANGER_POLICY_EVAL_SCORE_MAX_DISCOUNT_ACCESS_TYPES * accessPerms.size()) / serviceDef.getAccessTypes().size());
int customConditionsDiscount = RANGER_POLICY_EVAL_SCORE_MAX_DISCOUNT_CUSTOM_CONDITIONS - (RANGER_POLICY_EVAL_SCORE_CUSTOM_CONDITION_PENALTY * this.getCustomConditionsCount());
if(customConditionsDiscount > 0) {
evalOrder -= customConditionsDiscount;
}
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerOptimizedPolicyEvaluator.computeEvalOrder(), policyName:" + policy.getName() + ", priority:" + evalOrder);
}
return evalOrder;
}
@Override
protected boolean isAccessAllowed(String user, Set<String> userGroups, String accessType) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerOptimizedPolicyEvaluator.isAccessAllowed(" + user + ", " + userGroups + ", " + accessType + ")");
}
boolean ret = hasMatchablePolicyItem(user, userGroups, accessType) && super.isAccessAllowed(user, userGroups, accessType);
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerOptimizedPolicyEvaluator.isAccessAllowed(" + user + ", " + userGroups + ", " + accessType + "): " + ret);
}
return ret;
}
@Override
protected boolean hasMatchablePolicyItem(RangerAccessRequest request) {
boolean ret = false;
if (hasPublicGroup || hasCurrentUser || isOwnerMatch(request) || users.contains(request.getUser()) || CollectionUtils.containsAny(groups, request.getUserGroups())) {
if(request.isAccessTypeDelegatedAdmin()) {
ret = delegateAdmin;
} else if(hasAllPerms) {
ret = true;
} else {
ret = request.isAccessTypeAny() || accessPerms.contains(request.getAccessType());
}
}
return ret;
}
private boolean isOwnerMatch(RangerAccessRequest request) {
boolean ret = false;
if (hasResourceOwner) {
RangerAccessResource accessedResource = request.getResource();
String resourceOwner = accessedResource != null ? accessedResource.getOwnerUser() : null;
String user = request.getUser();
if (user != null && resourceOwner != null && user.equals(resourceOwner)) {
ret = true;
}
}
return ret;
}
private boolean hasMatchablePolicyItem(String user, Set<String> userGroups, String accessType) {
boolean ret = false;
if (hasPublicGroup || hasCurrentUser || users.contains(user) || CollectionUtils.containsAny(groups, userGroups)) {
boolean isAdminAccess = StringUtils.equals(accessType, RangerPolicyEngine.ADMIN_ACCESS);
if(isAdminAccess) {
ret = delegateAdmin;
} else if(hasAllPerms) {
ret = true;
} else {
boolean isAccessTypeAny = StringUtils.isEmpty(accessType) || StringUtils.equals(accessType, RangerPolicyEngine.ANY_ACCESS);
ret = isAccessTypeAny || accessPerms.contains(accessType);
}
}
return ret;
}
private void preprocessPolicyItems(List<? extends RangerPolicy.RangerPolicyItem> policyItems) {
if(CollectionUtils.isNotEmpty(policyItems)) {
for (RangerPolicy.RangerPolicyItem item : policyItems) {
delegateAdmin = delegateAdmin || item.getDelegateAdmin();
List<RangerPolicy.RangerPolicyItemAccess> policyItemAccesses = item.getAccesses();
for(RangerPolicy.RangerPolicyItemAccess policyItemAccess : policyItemAccesses) {
if (policyItemAccess.getIsAllowed()) {
String accessType = policyItemAccess.getType();
accessPerms.add(accessType);
}
}
groups.addAll(item.getGroups());
users.addAll(item.getUsers());
}
}
}
private boolean checkIfHasAllPerms() {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerOptimizedPolicyEvaluator.checkIfHasAllPerms()");
}
boolean result = true;
List<RangerServiceDef.RangerAccessTypeDef> serviceAccessTypes = getServiceDef().getAccessTypes();
for (RangerServiceDef.RangerAccessTypeDef serviceAccessType : serviceAccessTypes) {
if(! accessPerms.contains(serviceAccessType.getName())) {
result = false;
break;
}
}
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerOptimizedPolicyEvaluator.checkIfHasAllPerms(), " + result);
}
return result;
}
}