Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

As part of GEODE-3247, several options were analysed and, after considering the wealth of security holes and the difficulty of determining which methods deployed by the developer were intended to be available for queries and which were not, the decision was made to tighten up the Security and, by default, disallow any method call not explicitly whitelistedadded to an acceptance list.

After the solution was released and deployed by several users, the feedback wasn’t exactly the best and the overall feeling was that enabling this feature made OQL unusable. Our customers ended up changing the access of the attributes on their domain model to be public so the Field can be directly accessed by the query engine without requiring a method invocation (we shouldn't impose this restriction on the data model!!) or disabling OQL security altogether through the system property QueryService.allowUntrustedMethodInvocation.

...

  • Developers/Operators should be allowed to provide their own Method Authorization implementation.

  • The Method Authorization Implementation should be stored and retrieved through the cluster configuration service.

  • The current system property QueryService.allowUntrustedMethodInvocation should be kept but marked as deprecated.
  • The Method Authorization Implementation should be configurable/exchangeable in runtime (doesn’t need to be instantaneous update, eventual consistency is tolerable).The current system property QueryService.allowUntrustedMethodInvocation should be kept but marked as deprecated.

Current Implementation

MethodInvocationAuthorizer

...

The implementation first checks whether the method is whitelisted andpart of the allowed list, if it is, proceeds to check whether the user has the required privileges to execute queries on that particular region (DATA:READ:regionName). The current approach blacklists approach denies everything and only allows the some methods to be executed, this list can not be configured or changed in runtime, it’s hard coded and any addition/deletion requires a new release of the productThe list of currently whitelisted allowed methods is attached below.

...

  • If the SecurityManager is not enabled, or if the flag QueryService.allowUntrustedMethodInvocation is set as true, the MethodInvocationAuthorizer is created as a no-op (no security).
  • If the SecurityManager is enabled and the flag QueryService.allowUntrustedMethodInvocation is set as false (default), the RestrictedMethodInvocationAuthorizer is configured.

...

The class is used to execute a specific method on a target object while an OQL is being executed, is worth noticing that currently there is no context nor metadata about what the target class is or where it comes from.

As an example, within the expression user.isEnabled() the method would be isEnabled() and the target object would be the actual runtime type/class for user. The single entry point to this class is the invoke() method, which is called through CompiledOperation.evaluate(ExecutionContext context). This particular class, CompiledOperation, is just a node within the AST tree generated by the OQLParser, and it basically represents a method invocation in OQL.

Continuing with the example and assuming that the OQL contains contains the user.isEnabled() expression, below is a simplified pseudo code of how this is achieved in runtime:

...

The class is used to read a specific attribute from a target object while an OQL is being executed, is worth noticing again that currently there is no context nor metadata about what the target class is or where it comes from.

As an example, within the expression user.name the attribute would be name and the target object would be the actual runtime type/class for user. The single entry point to this class is the read(Object target) method, which is basically invoked through CompiledPath.evaluate(ExecutionContext context). This class, CompiledPath, is just a node within the AST tree generated by the OQLParser, and it . represents an identifier that follows a “dot” operator.

Assuming that the OQL is looking for user.name, below is a simplified pseudo code of how this is achieved in runtime:

...

      return Method.invoke(“user”)


    }

}

Proposal

Summary

Split the current Add a new top-level class, QueryInvocationAuthorizer, having the entire Method Authorization Implementation and split the current MethodInvocationAuthorizer in two different interfaces, each one having a clear and independent responsibility:

  • RegionInvocationAuthorizer
  • MethodInvocationAuthorizer

The QueryService will QueryInvocationAuthorizer will use an empty implementation of both interfaces if security is disabled or the flag QueryService.allowUntrustedMethodInvocation is set as true (as it works right now). If security is enabled, the default RegionInvocationAuthorizer will be used, and a default MethodInvocationAuthorizer will be set or, if configured, the custom implementation will be used. In runtime, the RegionInvocationAuthorizer will be always invoked first; if it succeeds and the user has privileges to query the region, the configured implementation of MethodInvocationAuthorizer will be executed.

...

The three of them, by default, will only allow the methods currently whitelisted allowed by RestrictedMethodInvocationAuthorizer plus whatever the internal implementation is configured to do. The JavaBeanAccessorBasedMethodAuthorizer and DataAwareBasedMethodAuthorizer cover the most common use cases and requires little to no configuration effort. For those use cases not covered, the user can choose to use the RegexBasedMethodAuthorizer, which allows them to configure which methods to whitelist allow directly through regex expressions. If none of the above is a good fit for a particular situation, the user ultimately has the option to provide its own implementation of the MethodInvocationAuthorizer interface and do whatever needed in order to allow/deny the execution of particular methods.

...

This section is just an overview and it contains some ideas of how the proposal could be achieved, no PoC has been done so far so the implementations details will might certainly change.

draw.io Diagram
bordertrue
viewerToolbartrue
fitWindowfalse
diagramNameTentativeClassDiagram
simpleViewerfalse
width
diagramWidth7911061
revision26


Interface RegionQueryInvocationAuthorizer Class QueryInvocationAuthorizer (Internal)

The default Default implementation, it should just check verify whether the user OQL can be executed on a particular region and whether the method is allowed or not by delegating to RegionInvocationAuthorizer and MethodInvocationAuthorizer respectively. 

Interface RegionQueryInvocationAuthorizer (Internal)

The default implementation should just check whether the user can execute queries on the region (DATA:READ:RegionName) and it should be invoked first in the chain execute queries on the region (DATA:READ:RegionName) and it should be invoked first in the chain (no point in checking a method if the user doesn’t even have read privileges on the region).

...

This interface is intended to be implemented by users wanting a custom authorization mechanism and by out of the box implementations as well. The interface will have only one method and it should throw NotAuthorizedException (non-checked exception) whenever it detects that the user should not be allowed to execute that particular method during the OQL execution.

public interface MethodInvocationAuthorizer  {

  void authorizeMethodInvocation(Method method, Region region) throws NotAuthorizedException;

...

   <class-name>test.Authorizer</class-name>

   <parameter name="whitelistDataBaseUrlallowedMethodsDataBaseUrl">

     <string>jdbc:mysql://myHost/whitelistDatabase<allowedMethodsDatabase</string>

   </parameter>

 </method-authorizer>

...

RegexBasedMethodAuthorizer

Whitelisted methods Methods allowed to be executed should match some regex expression(s) configured by the user, similar to what we currently do today with the PDX ReflectionBasedAutoSerializer. The implementation will have an internal structure containing already compiled Pattern instances and use them to verify the actual method that should be executed by the OQL engine, denying or allowing the execution based on the match result.

...

Risks / Unknowns / Disadvantages

  • Performance impact?.
  • Customers still need to configure “something” (the regex).
  • Customers need to learn regex expressions, if they don't do already.
  • Operators with little Regex knowledge can accidentally whitelist everythingallow everything, depending on which wildcards are used.
  • Customers need to configure “something” and have knowledge about regex expressions.

DataAwareBasedMethodAuthorizer

Allow the OQL engine to execute any method on any instance that is part of the object hierarchy inserted into the Geode Region. The idea is, basically, to allow everything as long as it has been inserted into the region by an authenticated user. It's the responsibility of the user to train operators and developers to not execute dangerous methods or mutator son mutators on their own objects (if any).

...

Some known dangerous methods (like getClass) should be disabled by default, however.

Advantage

  • No extra configuration needed.

  • Extremely flexible, user can execute any method.

  • Solves the general use case: deploy the domain model and , start executing OQL and invoking methods without further changes (other than setting this authorizer through configuration).

Risks / Unknowns / Disadvantages

  • How would it work for method invocation chain? (user.getAddressuser.getAddress().getZipCode().getId()).

  • How to get extra metadata about the object on which the method will be executed?. With the current implementation is not possible.

  • Java Reflection is already expensive, going up through the object hierarchy to find out whether the object is part of the region or not might greatly will definitely and negatively impact performance negatively.

JavaBeanAccessorBasedMethodAuthorizer

...

 throw new NotAuthorizedException(UNAUTHORIZED_STRING + methodName);

}

Advantages

  • No major changes needed.

  • Solves the general use case: most customers use get*/is* as the name for accessor methods and the configurable package restricts access only to methods from the domain model.

...

This section contains some examples showing how the feature should work, once it’s implemented, for different use cases. All examples assume that the cluster is already up and running with security enabled and that the current default method authorizer is configured (nothing is allowed). For the sake of simplicity, let’s also assume that the domain model is entirely contained within packages "order.model" and "tickets.model".

...

# Customer deploys the jar and configures the default authorizer JavaBeanAccessorBasedMethodAuthorizer

$> deploy --jar=/tmp/model-1.0.0.jar

...

# Customer deploys the jar and configures the default authorizer JavaBeanAccessorBasedMethodAuthorizer

$> deploy --jar=/tmp/model-1.0.0.jar

...

# Customer realizes that one developer did not follow the specification and multiple "calculateXXXX" methods exist in several classes that need to be accessed through OQL right away in productionmultiple "calculateXXXX" methods exist in several classes

# These methods need to be accessed through OQL right away in production, so they configure RegexBasedMethodAuthorizer with the required regex to allow default java bean accessors (get*|is*) + methods starting with "calculate*"

alter query-engine --method-authorizer=RegexBasedMethodAuthorizer{'patterns' : 'model.*(get|is|calculate)'}

...

# After removing all "calculateXXXX" methods the customer deploys the new model and re-configures the standard authorizerthe JavaBeanAccessorBasedMethodAuthorizer

deploy --jar=/tmp/model-2.0.0.jar

...

  private final static String WHITELISTMETHODS_ALLOWED_PER_REGION_VARIABLE_NAME KEY = "myEnvironmentVariable";

  private Map<String, List<String>> whitelistacceptList;


  private void parseEnvironment(String variableName) {

...

  public EnvironmentAwareAuthorizer() {

    parseEnvironment(WHITELISTMETHODS_ALLOWED_PER_REGION_VARIABLE_NAMEKEY);

  }


  @Override

  public void authorizeMethodInvocation(Method method, Region region) throws NotAuthorizedException {

...

    List<String> methodsAllowedWithinRegion = whitelistacceptList.get(regionName);

    if ((methodsAllowedWithinRegion != null) && (methodsAllowedWithinRegion.contains(methodName))) {

...

  • Easy to implement (+).
  • No major changes needed (+).
  • Not every method on the object might be safe to execute (-).
  • Customer needs to configure PDX AND also deploy the domain model (-).

...

ConfigurableAcceptListMethodAuthorizer

Keep the Method Authorization Implementation details internal and only provide support to configure the whitelist list of allowed methods to the users. The approach basically implies keeping the actual behaviour and RestrictedMethodInvocationAuthorizer implementation, but improving the class to make it "mutable" in terms of configuration so users can add or remove custom methods to the whitelist list of allowed methods in runtime. The methods whitelisted actually allowed should match exactly the ones to be executed by OQL, any difference will result in the method execution to be rejected by the query engine.

...

  • No major changes needed (+).
  • Easy to understand and configure (+).
  • Cumbersome Configuration (-).
    • Becomes a nightmare for huge data models.
    • User needs to add methods to the whitelist accepted list one by one, per class.
    • Maintenance complexity when the amount of whitelisted methods to allow increases.

AnnotationBasedMethodAuthorizer

...