/*
* 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.policyresourcematcher;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
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.RangerPolicy.RangerPolicyResource;
import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef;
import org.apache.ranger.plugin.model.RangerServiceDef;
import org.apache.ranger.plugin.model.validation.RangerServiceDefHelper;
import org.apache.ranger.plugin.policyengine.RangerAccessResource;
import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl;
import org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher;
import org.apache.ranger.plugin.resourcematcher.RangerResourceMatcher;
import com.google.common.collect.Sets;
public class RangerDefaultPolicyResourceMatcher implements RangerPolicyResourceMatcher {
private static final Log LOG = LogFactory.getLog(RangerDefaultPolicyResourceMatcher.class);
protected RangerServiceDef serviceDef;
protected RangerPolicy policy;
protected Map<String, RangerPolicyResource> policyResources;
private Map<String, RangerResourceMatcher> matchers;
private boolean needsDynamicEval;
private List<RangerResourceDef> firstValidResourceDefHierarchy;
/*
* For hive resource policy:
* lastNonAnyMatcherIndex will be set to
* 0 : if all matchers in policy are '*'; such as database=*, table=*, column=*
* 1 : database=hr, table=*, column=*
* 2 : database=<any>, table=employee, column=*
* 3 : database=<any>, table=<any>, column=ssn
*/
private int lastNonAnyMatcherIndex;
@Override
public void setServiceDef(RangerServiceDef serviceDef) {
this.serviceDef = serviceDef;
}
@Override
public void setPolicy(RangerPolicy policy) {
this.policy = policy;
setPolicyResources(policy == null ? null : policy.getResources());
}
@Override
public void setPolicyResources(Map<String, RangerPolicyResource> policyResources) {
this.policyResources = policyResources;
}
@Override
public boolean getNeedsDynamicEval() {
return needsDynamicEval;
}
@Override
public void init() {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerDefaultPolicyResourceMatcher.init()");
}
String errorText = "";
if(policyResources != null && !policyResources.isEmpty() && serviceDef != null) {
Set<String> policyResourceKeySet = policyResources.keySet();
RangerServiceDefHelper serviceDefHelper = new RangerServiceDefHelper(serviceDef, false);
int policyType = policy != null && policy.getPolicyType() != null ? policy.getPolicyType() : RangerPolicy.POLICY_TYPE_ACCESS;
Set<List<RangerResourceDef>> validResourceHierarchies = serviceDefHelper.getResourceHierarchies(policyType);
for (List<RangerResourceDef> validResourceHierarchy : validResourceHierarchies) {
Set<String> resourceDefNameSet = serviceDefHelper.getAllResourceNames(validResourceHierarchy);
if ((Sets.difference(policyResourceKeySet, resourceDefNameSet)).isEmpty()) {
firstValidResourceDefHierarchy = validResourceHierarchy;
break;
}
}
if (firstValidResourceDefHierarchy != null) {
List<String> resourceDefNameOrderedList = serviceDefHelper.getAllResourceNamesOrdered(firstValidResourceDefHierarchy);
boolean foundGapsInResourceSpecs = false;
boolean skipped = false;
for (String resourceDefName : resourceDefNameOrderedList) {
RangerPolicyResource policyResource = policyResources.get(resourceDefName);
if (policyResource == null) {
skipped = true;
} else if (skipped) {
foundGapsInResourceSpecs = true;
break;
}
}
if (foundGapsInResourceSpecs) {
errorText = "policyResources does not specify contiguous sequence in any valid resourcedef hiearchy.";
if (LOG.isDebugEnabled()) {
LOG.debug("RangerDefaultPolicyResourceMatcher.init() failed: Gaps found in policyResources, internal error, skipping..");
}
firstValidResourceDefHierarchy = null;
} else {
matchers = new HashMap<>();
for (RangerResourceDef resourceDef : firstValidResourceDefHierarchy) {
String resourceName = resourceDef.getName();
RangerPolicyResource policyResource = policyResources.get(resourceName);
if (policyResource != null) {
RangerResourceMatcher matcher = createResourceMatcher(resourceDef, policyResource);
if (matcher != null) {
if (!needsDynamicEval && matcher.getNeedsDynamicEval()) {
needsDynamicEval = true;
}
matchers.put(resourceName, matcher);
if (!matcher.isMatchAny()) {
lastNonAnyMatcherIndex = matchers.size();
}
} else {
LOG.error("failed to find matcher for resource " + resourceName);
}
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("RangerDefaultPolicyResourceMatcher.init() - no matcher created for " + resourceName + ". Continuing ...");
}
}
}
}
} else {
errorText = "policyResources elements are not part of any valid resourcedef hierarchy.";
}
} else {
errorText = " policyResources is null or empty, or serviceDef is null.";
}
if(matchers == null) {
Set<String> policyResourceKeys = policyResources == null ? null : policyResources.keySet();
StringBuilder sb = new StringBuilder();
if (CollectionUtils.isNotEmpty(policyResourceKeys)) {
for (String policyResourceKeyName : policyResourceKeys) {
sb.append(" ").append(policyResourceKeyName).append(" ");
}
}
String keysString = sb.toString();
String serviceDefName = serviceDef == null ? "" : serviceDef.getName();
String validHierarchy = "";
if (serviceDef != null && CollectionUtils.isNotEmpty(firstValidResourceDefHierarchy)) {
RangerServiceDefHelper serviceDefHelper = new RangerServiceDefHelper(serviceDef, false);
List<String> resourceDefNameOrderedList = serviceDefHelper.getAllResourceNamesOrdered(firstValidResourceDefHierarchy);
for (String resourceDefName : resourceDefNameOrderedList) {
validHierarchy += " " + resourceDefName + " ";
}
}
LOG.warn("RangerDefaultPolicyResourceMatcher.init() failed: " + errorText + " (serviceDef=" + serviceDefName + ", policyResourceKeys=" + keysString
+ ", validHierarchy=" + validHierarchy + ")");
}
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerDefaultPolicyResourceMatcher.init()");
}
}
@Override
public RangerServiceDef getServiceDef() {
return serviceDef;
}
@Override
public RangerResourceMatcher getResourceMatcher(String resourceName) {
return matchers != null ? matchers.get(resourceName) : null;
}
@Override
public boolean isMatch(Map<String, RangerPolicyResource> resources, Map<String, Object> evalContext) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerDefaultPolicyResourceMatcher.isMatch(" + resources + ", " + evalContext + ")");
}
boolean ret = false;
if(serviceDef != null && serviceDef.getResources() != null) {
Collection<String> resourceKeys = resources == null ? null : resources.keySet();
Collection<String> policyKeys = matchers == null ? null : matchers.keySet();
boolean keysMatch = CollectionUtils.isEmpty(resourceKeys) || (policyKeys != null && policyKeys.containsAll(resourceKeys));
if(keysMatch) {
for(RangerResourceDef resourceDef : serviceDef.getResources()) {
String resourceName = resourceDef.getName();
RangerPolicyResource resourceValues = resources == null ? null : resources.get(resourceName);
RangerResourceMatcher matcher = matchers == null ? null : matchers.get(resourceName);
// when no value exists for a resourceName, consider it a match only if: policy doesn't have a matcher OR matcher allows no-value resource
if(resourceValues == null || CollectionUtils.isEmpty(resourceValues.getValues())) {
ret = matcher == null || matcher.isMatch(null, null);
} else if(matcher != null) {
for(String resourceValue : resourceValues.getValues()) {
ret = matcher.isMatch(resourceValue, evalContext);
if(! ret) {
break;
}
}
}
if(! ret) {
break;
}
}
} else {
if(LOG.isDebugEnabled()) {
LOG.debug("isMatch(): keysMatch=false. resourceKeys=" + resourceKeys + "; policyKeys=" + policyKeys);
}
}
}
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerDefaultPolicyResourceMatcher.isMatch(" + resources + ", " + evalContext + "): " + ret);
}
return ret;
}
@Override
public boolean isCompleteMatch(RangerAccessResource resource, Map<String, Object> evalContext) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerDefaultPolicyResourceMatcher.isCompleteMatch(" + resource + ", " + evalContext + ")");
}
boolean ret = false;
if(serviceDef != null && serviceDef.getResources() != null) {
Collection<String> resourceKeys = resource == null ? null : resource.getKeys();
Collection<String> policyKeys = matchers == null ? null : matchers.keySet();
boolean keysMatch = resourceKeys != null && policyKeys != null && CollectionUtils.isEqualCollection(resourceKeys, policyKeys);
if(keysMatch) {
for(RangerResourceDef resourceDef : serviceDef.getResources()) {
String resourceName = resourceDef.getName();
String resourceValue = resource == null ? null : resource.getValue(resourceName);
RangerResourceMatcher matcher = matchers == null ? null : matchers.get(resourceName);
if(StringUtils.isEmpty(resourceValue)) {
ret = matcher == null || matcher.isCompleteMatch(resourceValue, evalContext);
} else {
ret = matcher != null && matcher.isCompleteMatch(resourceValue, evalContext);
}
if(! ret) {
break;
}
}
} else {
if(LOG.isDebugEnabled()) {
LOG.debug("isCompleteMatch(): keysMatch=false. resourceKeys=" + resourceKeys + "; policyKeys=" + policyKeys);
}
}
}
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerDefaultPolicyResourceMatcher.isCompleteMatch(" + resource + ", " + evalContext + "): " + ret);
}
return ret;
}
@Override
public boolean isCompleteMatch(Map<String, RangerPolicyResource> resources, Map<String, Object> evalContext) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerDefaultPolicyResourceMatcher.isCompleteMatch(" + resources + ", " + evalContext + ")");
}
boolean ret = false;
if(serviceDef != null && serviceDef.getResources() != null) {
Collection<String> resourceKeys = resources == null ? null : resources.keySet();
Collection<String> policyKeys = matchers == null ? null : matchers.keySet();
boolean keysMatch = resourceKeys != null && policyKeys != null && CollectionUtils.isEqualCollection(resourceKeys, policyKeys);
if(keysMatch) {
for(RangerResourceDef resourceDef : serviceDef.getResources()) {
String resourceName = resourceDef.getName();
RangerPolicyResource resourceValues = resources == null ? null : resources.get(resourceName);
RangerPolicyResource policyValues = policyResources == null ? null : policyResources.get(resourceName);
if(resourceValues == null || CollectionUtils.isEmpty(resourceValues.getValues())) {
ret = (policyValues == null || CollectionUtils.isEmpty(policyValues.getValues()));
} else if(policyValues != null && CollectionUtils.isNotEmpty(policyValues.getValues())) {
ret = CollectionUtils.isEqualCollection(resourceValues.getValues(), policyValues.getValues());
}
if(! ret) {
break;
}
}
} else {
if(LOG.isDebugEnabled()) {
LOG.debug("isCompleteMatch(): keysMatch=false. resourceKeys=" + resourceKeys + "; policyKeys=" + policyKeys);
}
}
}
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerDefaultPolicyResourceMatcher.isCompleteMatch(" + resources + ", " + evalContext + "): " + ret);
}
return ret;
}
@Override
public boolean isMatch(RangerAccessResource resource, Map<String, Object> evalContext) {
return isMatch(resource, MatchScope.SELF_OR_ANCESTOR, evalContext);
}
@Override
public boolean isMatch(RangerPolicy policy, MatchScope scope, Map<String, Object> evalContext) {
boolean ret = false;
MatchType matchType = MatchType.NONE;
Map<String, RangerPolicyResource> resources = policy.getResources();
if (MapUtils.isNotEmpty(resources)) {
RangerAccessResourceImpl accessResource = new RangerAccessResourceImpl();
accessResource.setServiceDef(serviceDef);
// Build up accessResource resourceDef by resourceDef.
// For each resourceDef,
// examine policy-values one by one.
// The first value that is acceptable, that is,
// value matches in any way, is used for that resourceDef, and
// next resourceDef is processed.
// If none of the values matches, the policy as a whole definitely will not match,
// therefore, the match is failed
// After all resourceDefs are processed, and some match is achieved at every
// level, the final matchType (which is for the entire policy) is checked against
// requested scope to determine the match-result.
// Unit tests in TestDefaultPolicyResourceForPolicy.java, test_defaultpolicyresourcematcher_for_policy.json,
// and test_defaultpolicyresourcematcher_for_hdfs_policy.json
for (RangerResourceDef resourceDef : firstValidResourceDefHierarchy) {
ret = false;
matchType = MatchType.NONE;
String name = resourceDef.getName();
RangerPolicyResource policyResource = resources.get(name);
if (policyResource != null) {
for (String value : policyResource.getValues()) {
accessResource.setValue(name, value);
matchType = getMatchType(accessResource, evalContext);
if (matchType != MatchType.NONE) { // One value for this resourceDef matched
ret = true;
break;
}
}
}
if (!ret) { // None of the values specified for this resourceDef matched, no point in continuing with next resourceDef
break;
}
}
ret = ret && isMatch(scope, matchType);
}
return ret;
}
@Override
public boolean isMatch(RangerAccessResource resource, MatchScope scope, Map<String, Object> evalContext) {
final boolean ret;
MatchType matchType = getMatchType(resource, evalContext);
ret = isMatch(scope, matchType);
return ret;
}
@Override
public MatchType getMatchType(RangerAccessResource resource, Map<String, Object> evalContext) {
if (LOG.isDebugEnabled()) {
LOG.debug("==> RangerDefaultPolicyResourceMatcher.getMatchType(" + resource + evalContext + ")");
}
int matchersSize = matchers == null ? 0 : matchers.size();
int resourceKeysSize = resource == null || resource.getKeys() == null ? 0 : resource.getKeys().size();
MatchType ret = MatchType.NONE;
if (!isValid(resource)) {
ret = MatchType.NONE;
} else if (matchersSize == 0 || lastNonAnyMatcherIndex == 0) {
ret = resourceKeysSize == 0 ? MatchType.SELF : MatchType.ANCESTOR;
} else if (resourceKeysSize == 0) {
ret = MatchType.DESCENDANT;
} else {
int index = 0;
for (RangerResourceDef resourceDef : firstValidResourceDefHierarchy) {
String resourceName = resourceDef.getName();
RangerResourceMatcher matcher = matchers.get(resourceName);
String resourceValue = resource.getValue(resourceName);
if (resourceValue != null) {
if (matcher != null) {
index++;
if (matcher.isMatch(resourceValue, evalContext)) {
ret = index == resourceKeysSize && matcher.isMatchAny() ? MatchType.ANCESTOR : MatchType.SELF;
} else {
ret = MatchType.NONE;
break;
}
} else {
// More resource-levels than matchers
ret = MatchType.ANCESTOR;
break;
}
} else {
if (matcher != null) {
// More matchers than resource-levels
if (index >= lastNonAnyMatcherIndex) {
// All AnyMatch matchers after this
ret = MatchType.ANCESTOR;
} else {
ret = MatchType.DESCENDANT;
}
} else {
// Common part of several possible hierarchies matched
if (resourceKeysSize > index) {
ret = MatchType.ANCESTOR;
}
}
break;
}
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("<== RangerDefaultPolicyResourceMatcher.getMatchType(" + resource + evalContext + "): " + ret);
}
return ret;
}
private boolean isValidResourceDefHierachyForResource(List<RangerResourceDef> resourceHierarchy, RangerAccessResource resource) {
boolean foundAllResourceKeys = true;
for (String resourceKey : resource.getKeys()) {
boolean found = false;
for (RangerResourceDef resourceDef : resourceHierarchy) {
if (resourceDef.getName().equals(resourceKey)) {
found = true;
break;
}
}
if (!found) {
foundAllResourceKeys = false;
break;
}
}
return foundAllResourceKeys;
}
private boolean isValid(RangerAccessResource resource) {
if (LOG.isDebugEnabled()) {
LOG.debug("==> RangerDefaultPolicyResourceMatcher.isValid(" + resource + ")");
}
boolean ret = true;
if (matchers != null && resource != null && resource.getKeys() != null) {
if (matchers.keySet().containsAll(resource.getKeys()) || resource.getKeys().containsAll(matchers.keySet())) {
List<RangerResourceDef> aValidHierarchy = null;
if (resource.getKeys().containsAll(matchers.keySet()) && resource.getKeys().size() > matchers.keySet().size()) {
if (isValidResourceDefHierachyForResource(firstValidResourceDefHierarchy, resource)) {
aValidHierarchy = firstValidResourceDefHierarchy;
} else {
RangerServiceDefHelper serviceDefHelper = new RangerServiceDefHelper(serviceDef, false);
int policyType = policy != null && policy.getPolicyType() != null ? policy.getPolicyType() : RangerPolicy.POLICY_TYPE_ACCESS;
Set<List<RangerResourceDef>> validResourceHierarchies = serviceDefHelper.getResourceHierarchies(policyType);
for (List<RangerResourceDef> resourceHierarchy : validResourceHierarchies) {
if (resourceHierarchy == firstValidResourceDefHierarchy) { // Pointer comparison
// firstValidResourceDefHierarchy is already checked before and it does not match
continue;
}
if (isValidResourceDefHierachyForResource(resourceHierarchy, resource)) {
aValidHierarchy = resourceHierarchy;
break;
}
}
}
} else {
aValidHierarchy = firstValidResourceDefHierarchy;
}
if (aValidHierarchy != null) {
boolean skipped = false;
for (RangerResourceDef resourceDef : aValidHierarchy) {
String resourceName = resourceDef.getName();
String resourceValue = resource.getValue(resourceName);
if (resourceValue == null) {
if (!skipped) {
skipped = true;
}
} else {
if (skipped) {
ret = false;
break;
}
}
}
} else {
ret = false;
}
} else {
ret = false;
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("<== RangerDefaultPolicyResourceMatcher.isValid(" + resource + "): " + ret);
}
return ret;
}
private boolean isMatch(final MatchScope scope, final MatchType matchType) {
final boolean ret;
switch (scope) {
case SELF_OR_ANCESTOR_OR_DESCENDANT: {
ret = matchType != MatchType.NONE;
break;
}
case SELF: {
ret = matchType == MatchType.SELF;
break;
}
case SELF_OR_DESCENDANT: {
ret = matchType == MatchType.SELF || matchType == MatchType.DESCENDANT;
break;
}
case SELF_OR_ANCESTOR: {
ret = matchType == MatchType.SELF || matchType == MatchType.ANCESTOR;
break;
}
case DESCENDANT: {
ret = matchType == MatchType.DESCENDANT;
break;
}
case ANCESTOR: {
ret = matchType == MatchType.ANCESTOR;
break;
}
default:
ret = matchType != MatchType.NONE;
break;
}
return ret;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
toString(sb);
return sb.toString();
}
@Override
public StringBuilder toString(StringBuilder sb) {
sb.append("RangerDefaultPolicyResourceMatcher={");
sb.append("matchers={");
if(matchers != null) {
for(RangerResourceMatcher matcher : matchers.values()) {
sb.append("{").append(matcher).append("} ");
}
}
sb.append("} ");
sb.append("}");
return sb;
}
protected static RangerResourceMatcher createResourceMatcher(RangerResourceDef resourceDef, RangerPolicyResource resource) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerDefaultPolicyResourceMatcher.createResourceMatcher(" + resourceDef + ", " + resource + ")");
}
RangerResourceMatcher ret = null;
if (resourceDef != null) {
String resName = resourceDef.getName();
String clsName = resourceDef.getMatcher();
if (!StringUtils.isEmpty(clsName)) {
try {
@SuppressWarnings("unchecked")
Class<RangerResourceMatcher> matcherClass = (Class<RangerResourceMatcher>) Class.forName(clsName);
ret = matcherClass.newInstance();
} catch (Exception excp) {
LOG.error("failed to instantiate resource matcher '" + clsName + "' for '" + resName + "'. Default resource matcher will be used", excp);
}
}
if (ret == null) {
ret = new RangerDefaultResourceMatcher();
}
if (ret != null) {
ret.setResourceDef(resourceDef);
ret.setPolicyResource(resource);
ret.init();
}
} else {
LOG.error("RangerDefaultPolicyResourceMatcher: RangerResourceDef is null");
}
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerDefaultPolicyResourceMatcher.createResourceMatcher(" + resourceDef + ", " + resource + "): " + ret);
}
return ret;
}
}