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
- Order by traffic volume - Put high-traffic API policies first
- Keep policies simple - Avoid nested conditions and complex logic
- Use specific functions - Choose
scope()
overany_scope()
for single values - Use
input.parsed_path
- For exact path matches instead of glob patterns - Balance policy trees - Avoid one-sided weighted structures
- Check methods selectively - Only when different permissions are required
❌ Don’ts
- Don’t create complex helper functions - They slow down indexing
- Don’t use glob patterns unnecessarily - Use exact matches when possible
- Don’t check methods redundantly - Only when policies actually differ
- Don’t use generic functions - When specific ones are available
- Don’t create deeply nested conditions - Keep rules flat and simple