Policy Optimizations

Policy Optimizations

Overview

This guide provides key strategies and best practices for optimizing OPA policy performance in Maersk Authorization Service. Following these recommendations will help you create efficient, maintainable policies that scale with your application’s needs.

Key Optimization Strategies

1. Order API Policies by Traffic

When defining policies for APIs, order them based on the expected traffic. Policies for high-traffic APIs should be placed at the top of the policy list, while those for lower-traffic APIs should follow. This ordering allows OPA to evaluate the most frequently accessed policies first, reducing the overall evaluation time and improving response times for users.

Tip

Place your most frequently accessed API policies at the beginning of your policy rules to minimize evaluation time.

2. Simplify Policies for Rule Indexing

Keeping policies as simple as possible is crucial for enabling efficient rule indexing, which is enabled by default in OPA. Simple policies allow OPA to quickly index and retrieve rules, leading to faster evaluations. Avoid complex logic and nested conditions where possible, as these can hinder the indexing process and slow down policy evaluation.

Example of a Simple Policy ✅

allow if {
    glob.match("/companies\\?*", null, http_request.path)
    token.allow
    token.any_role(Oauth2PublicRoles)
}

Example of a Complex Policy ❌

allow if {
    glob.match("/contacts/*", null, http_request.path)
    token.allow
    has_contact_access(http_request.headers)
}

has_contact_access(headers) if {
    headers["api-version"] == "2"
    token.any_scope_pattern(Oauth2System)
}

has_contact_access(headers) if {
    not headers["api-version"] == "2"
    token.any_role(Oauth2InternalRoles)
}

Tip

Avoid Complex Logic: Nested conditions and helper functions can slow down policy evaluation. Keep rules as flat and simple as possible.

3. Avoid One-Sided Weighted Trees

When structuring policies, it is important to avoid creating a one-sided weighted tree. A balanced approach to policy structuring ensures that OPA can efficiently index and evaluate rules. A well-balanced policy tree allows for quicker access to relevant rules, improving the overall performance of policy evaluations.

In scenarios where multiple APIs share the same path but differ in their HTTP methods (e.g., /contacts with GET and POST methods), it is advisable to check the request method only when there are conflicting policies. By doing so, you can avoid unnecessary evaluations for requests that do not require method differentiation. This targeted approach minimizes overhead and enhances performance.

Example of an Unbalanced Tree ❌

# Unnecessary method check for every request
allow if {
    glob.match("/contacts", null, http_request.path)
    http_request.method == "GET"
    token.allow
    token.any_role(["BasicCustomer"])
}

allow if {
    glob.match("/contacts", null, http_request.path)
    http_request.method == "POST"
    token.allow
    token.any_role(["BasicCustomer"])
}

Example of Balanced Tree ✅

# Check method only when policies differ
allow if {
    glob.match("/contacts", null, http_request.path)
    token.allow
    token.any_role(["BasicCustomer"])
}

Example of Request Method Check Policy

# Only check method when different permissions are required
allow if {
    glob.match("/contacts", null, http_request.path)
    http_request.method == "GET"
    token.allow
    token.any_role(["BasicCustomer", "PublicUser"])
}

allow if {
    glob.match("/contacts", null, http_request.path)
    http_request.method == "POST"
    token.allow
    token.any_role(["BasicCustomer"])  # More restrictive for writes
}

Functional Improvements

The Policy generated by the policy generator is generic and simple and can be redundant in some cases. Consider the following improvements on top of the generated policy:

1. Use input.parsed_path Instead of glob.match()

If your API path doesn’t have a path variable or if you don’t need dynamic path checking, replace glob.match() with input.parsed_path for better performance.

Optimized Path Matching Example

package envoy.authz
import rego.v1
import input.attributes.request.http as http_request
import input.parsed_path as path
import data.com.maersk.global.authz as token

default allow := false

oauth2PublicScope := ["customer.read"]

allow if {
    # Here request path is /actuator/health/liveness
    glob.match("/actuator/*", null, http_request.path)
}

allow if {
    # Here request path is /public-data?customerCode=1234567890
    path == ["public-data"]
    token.allow
    token.scope("customer.read")
}

allow if {
    # Here request path is /relationships/1234567890
    glob.match("/relationships/*", null, http_request.path)
    token.allow
    token.scope("customer.read")
}

Tip

Performance Boost: Using input.parsed_path for exact path matches is significantly faster than glob.match() patterns.

2. Optimize Function Selection

Replace check_access() Based on Token Type

The check_access() function can validate both id_token (roles) and access_token (scopes), but using more specific functions can improve performance:

For Access Token Only Applications:

# Replace check_access() with any_scope()
allow if {
    token.any_scope(["customer.read", "customer.write"])
}

For ID Token Only Applications:

# Replace check_access() with any_role()
allow if {
    token.any_role(["BasicCustomer", "MaerskInternal"])
}

Use Specific Functions for Single Values

The policy generator defines allowed roles/scopes as arrays of strings. If you need to check only one scope/role, replace any_scope() or any_role() with scope() or role():

Single Scope Check:

# Instead of: token.any_scope(["customer.read"])
allow if {
    token.scope("customer.read")
}

Single Role Check:

# Instead of: token.any_role(["BasicCustomer"])
allow if {
    token.role("BasicCustomer")
}

Performance Best Practices Summary

✅ Do’s

  1. Order by traffic volume - Put high-traffic API policies first
  2. Keep policies simple - Avoid nested conditions and complex logic
  3. Use specific functions - Choose scope() over any_scope() for single values
  4. Use input.parsed_path - For exact path matches instead of glob patterns
  5. Balance policy trees - Avoid one-sided weighted structures
  6. Check methods selectively - Only when different permissions are required

❌ Don’ts

  1. Don’t create complex helper functions - They slow down indexing
  2. Don’t use glob patterns unnecessarily - Use exact matches when possible
  3. Don’t check methods redundantly - Only when policies actually differ
  4. Don’t use generic functions - When specific ones are available
  5. Don’t create deeply nested conditions - Keep rules flat and simple