blob: 7a4d8fa08402b0dbab5d0b05832962106da37b9e [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.geode.cache.query.security;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.Declarable;
import org.apache.geode.cache.Region;
/**
* An immutable and thread-safe {@link MethodInvocationAuthorizer} that only allows the execution of
* those methods matching the configured regular expression.
* <p/>
*
* Some known dangerous methods, like {@link Object#getClass()}, are also rejected by this
* authorizer implementation, no matter whether the method matches the configured regular
* expressions
* or not (see {@link RestrictedMethodAuthorizer#isPermanentlyForbiddenMethod(Method, Object)}).
* <p/>
*
* When correctly configured, this authorizer implementation addresses the four known security
* risks: {@code Java Reflection}, {@code Cache Modification}, {@code Region Modification} and
* {@code Region Entry Modification}.
* <p/>
*
* For the above statement to remain true, however, the regular expressions used must be
* exhaustively studied and configured so no mutator methods match. If the regular expressions are
* not restrictive enough, the {@code Region Entry Modification} security risk still exists: users
* with the {@code DATA:READ:RegionName} privileges will be able to execute methods (even those
* modifying the entry) on the objects stored within the region and on instances used as bind
* parameters of the query, so this authorizer must be used with extreme care.
* <p/>
*
* Usage of this authorizer implementation is only recommended for scenarios on which the user or
* operator knows exactly what code is deployed to the cluster, how and when; allowing a correct
* configuration of the regular expressions. It might also be used on clusters on which the entries
* stored are immutable.
* <p/>
*
* @see org.apache.geode.cache.Cache
* @see org.apache.geode.cache.query.security.MethodInvocationAuthorizer
* @see org.apache.geode.cache.query.security.RestrictedMethodAuthorizer
*/
public final class RegExMethodAuthorizer implements MethodInvocationAuthorizer {
private static final String GEODE_BASE_PACKAGE = "org.apache.geode";
static final String NULL_CACHE_MESSAGE = "Cache should be provided to configure this authorizer.";
static final String NULL_AUTHORIZER_MESSAGE =
"RestrictedMethodAuthorizer should be provided to create this authorizer.";
static final String NULL_REGULAR_EXPRESSIONS_MESSAGE =
"A set of regular expression should be provided to configure this authorizer.";
private final Set<String> allowedPatterns;
private final Set<Pattern> compiledPatterns;
private final RestrictedMethodAuthorizer restrictedMethodAuthorizer;
private Set<Pattern> compilePatterns() {
Set<Pattern> patterns = new HashSet<>();
allowedPatterns.forEach(regEx -> patterns.add(Pattern.compile(regEx)));
return patterns;
}
/**
* Returns an unmodifiable view of the regular expressions used to configure this authorizer.
* This method can be used to get "read-only" access to the set containing the regular expressions
* that will be used to determine whether a method is allowed or not.
*
* @return an unmodifiable view of the regular expressions used to configure this authorizer.
*/
public Set<String> getAllowedPatterns() {
return allowedPatterns;
}
/**
* Creates a {@code RegExMethodAuthorizer} object and initializes it so it can be safely used
* in a multi-threaded environment.
* <p/>
*
* Applications can use this constructor as part of the initialization for custom authorizers
* (see {@link Declarable#initialize(Cache, Properties)}, when using a declarative approach.
*
* @param cache the {@code Cache} instance that owns this authorizer, required in order to
* configure the default {@link RestrictedMethodAuthorizer}.
* @param allowedPatterns the regular expressions that will be used to determine whether a method
* is authorized or not.
*/
public RegExMethodAuthorizer(Cache cache, Set<String> allowedPatterns) {
Objects.requireNonNull(cache, NULL_CACHE_MESSAGE);
Objects.requireNonNull(allowedPatterns, NULL_REGULAR_EXPRESSIONS_MESSAGE);
this.allowedPatterns = Collections.unmodifiableSet(allowedPatterns);
this.compiledPatterns = Collections.unmodifiableSet(compilePatterns());
this.restrictedMethodAuthorizer = new RestrictedMethodAuthorizer(cache);
}
/**
* Creates a {@code RegExMethodAuthorizer} object and initializes it so it can be safely used
* in a multi-threaded environment.
* <p/>
*
* @param restrictedMethodAuthorizer the default {@code RestrictedMethodAuthorizer} to use.
* @param allowedPatterns the regular expressions that will be used to determine whether a method
* is authorized or not.
*/
public RegExMethodAuthorizer(RestrictedMethodAuthorizer restrictedMethodAuthorizer,
Set<String> allowedPatterns) {
Objects.requireNonNull(allowedPatterns, NULL_REGULAR_EXPRESSIONS_MESSAGE);
Objects.requireNonNull(restrictedMethodAuthorizer, NULL_AUTHORIZER_MESSAGE);
this.allowedPatterns = Collections.unmodifiableSet(allowedPatterns);
this.compiledPatterns = Collections.unmodifiableSet(compilePatterns());
this.restrictedMethodAuthorizer = restrictedMethodAuthorizer;
}
/**
* Executes the authorization logic to determine whether the {@code method} is allowed to be
* executed on the {@code target} object instance.
* If the {@code target} object is an instance of {@link Region}, this methods also ensures that
* the user has the {@code DATA:READ} permission granted for the target {@link Region}.
* <p/>
*
* @param method the {@link Method} that should be authorized.
* @param target the {@link Object} on which the {@link Method} will be executed.
* @return {@code true} if the {@code method} can be executed on on the {@code target} instance,
* {@code false} otherwise.
*
* @see org.apache.geode.cache.query.security.MethodInvocationAuthorizer
*/
@Override
public boolean authorize(Method method, Object target) {
// Return false for known dangerous methods.
if (restrictedMethodAuthorizer.isPermanentlyForbiddenMethod(method, target)) {
return false;
}
// If the target object belongs to Geode, make sure the method is considered safe.
if (target.getClass().getPackage().getName().startsWith(GEODE_BASE_PACKAGE)) {
return restrictedMethodAuthorizer.isAllowedGeodeMethod(method, target);
}
// Compare fully qualified method name against compiled expressions.
String fullyQualifiedMethodName = target.getClass().getName() + "." + method.getName();
if (compiledPatterns.stream()
.anyMatch(pattern -> pattern.matcher(fullyQualifiedMethodName).matches())) {
return true;
}
// Delegate to the default authorizer.
return restrictedMethodAuthorizer.authorize(method, target);
}
}