Policy Libraries
Policy Libraries contain reusable Rego code that can be shared across multiple policies within a PolicyDomain.
Overview
Libraries help you:
- Reduce duplication: Write helper functions once
- Improve maintainability: Update logic in one place
- Organize code: Separate concerns into focused packages
Defining a Library
spec:
policy-libraries:
- mrn: "mrn:iam:library:helpers"
name: helpers
description: "Common helper functions"
rego: |
package helpers
# Glob pattern matching
match_any(patterns, value) {
glob.match(patterns[_], [], value)
}
# Check if principal has a specific role
has_role(role) {
input.principal.mroles[_] == role
}
# Check if principal is in a group
in_group(group) {
input.principal.mgroups[_] == group
}
Policy Libraries have specific package naming requirements:
-
Must NOT use
package authz— Theauthzpackage is reserved for authorization policies (those defined in thepoliciessection that make access decisions). Libraries use their own unique package names (e.g.,package helpers,package utils). -
Must be unique across all dependencies — When an authorization policy imports multiple libraries, each library must have a distinct package name. If two libraries both declare
package utils, a collision will occur.
Best practice: Use descriptive, organization-specific package names to avoid collisions (e.g., package myorg.helpers or package acme.validation). This ensures libraries can be safely combined in any policy.
Using Libraries in Policies
Declare the library as a dependency and import it:
spec:
policies:
- mrn: "mrn:iam:policy:my-policy"
name: my-policy
dependencies:
- "mrn:iam:library:helpers"
rego: |
package authz
import data.helpers
default allow = false
allow {
helpers.has_role("mrn:iam:role:admin")
}
Library Dependencies
Libraries can depend on other libraries:
spec:
policy-libraries:
- mrn: &utils "mrn:iam:library:utils"
name: utils
rego: |
package utils
ro_operations := {"*:read", "*:list", "*:get"}
- mrn: "mrn:iam:library:access"
name: access
dependencies:
- *utils
rego: |
package access
import data.utils
is_readonly {
glob.match(utils.ro_operations[_], [], input.operation)
}
Common Library Patterns
The Utils Library Pattern
A recommended pattern is to create a utils library with common helpers that are used across multiple policies. This eliminates duplication and ensures consistency:
spec:
policy-libraries:
- mrn: &lib-utils "mrn:iam:library:utils"
name: utils
description: "Common utility functions"
rego: |
package utils
import rego.v1
# Check if request has a valid principal (authenticated)
has_principal if {
input.principal != {}
input.principal.sub != ""
}
# Domain-specific library can coexist with utils
- mrn: &lib-domain-helpers "mrn:iam:library:domain-helpers"
name: domain-helpers
description: "Domain-specific helper functions"
rego: |
package domain_helpers
# ... domain-specific helpers
Policies can then use the utils library:
policies:
- mrn: "mrn:iam:policy:require-auth"
name: require-auth
dependencies:
- *lib-utils
rego: |
package authz
import rego.v1
import data.utils
# Tri-level: negative=DENY, 0=GRANT, positive=GRANT Override
# Default deny - only grant if authenticated
default allow = -1
# Grant authenticated requests
allow = 0 if utils.has_principal
This pattern:
- Reduces duplication: Define
has_principalonce, use everywhere - Ensures consistency: All policies use the same authentication check
- Simplifies maintenance: Update the check in one place
Using Multiple Libraries
Policies can depend on multiple libraries simultaneously. This is useful when combining general utilities with domain-specific helpers:
policies:
- mrn: "mrn:iam:policy:mcp-operation"
dependencies:
- *lib-utils # Common utilities
- *lib-mcp-helpers # MCP-specific helpers
rego: |
package authz
import rego.v1
import data.utils
import data.mcp_helpers
default allow = -1
allow = 1 if mcp_helpers.is_health_check
allow = 0 if utils.has_principal
Operation Helpers
package operations
# Read-only operations
ro_operations := {
"*:read",
"*:list",
"*:get",
"*:head"
}
# Write operations
write_operations := {
"*:create",
"*:update",
"*:write",
"*:delete"
}
is_read_only {
glob.match(ro_operations[_], [], input.operation)
}
is_write {
glob.match(write_operations[_], [], input.operation)
}
Role Helpers
package roles
admin_roles := {
"mrn:iam:role:admin",
"mrn:iam:role:superadmin"
}
is_admin {
input.principal.mroles[_] in admin_roles
}
has_any_role(required_roles) {
some role in input.principal.mroles
role in required_roles
}
Resource Helpers
package resources
is_owner {
input.principal.sub == input.resource.owner
}
clearance_levels := {
"LOW": 1,
"MODERATE": 2,
"HIGH": 3,
"MAXIMUM": 4,
"UNASSIGNED": 5
}
has_clearance {
clearance_levels[input.principal.mclearance] >= clearance_levels[input.resource.classification]
}
Validation Helpers
package validation
# Check if principal is authenticated
is_authenticated {
input.principal != {}
input.principal.sub != ""
}
# Check if principal has a valid JWT
has_valid_jwt {
input.principal.exp > time.now_ns() / 1000000000
}
Cross-Domain References
Reference libraries from other PolicyDomains:
spec:
policies:
- mrn: "mrn:iam:policy:my-policy"
name: my-policy
dependencies:
- "other-domain/common-utils" # Cross-domain reference
rego: |
package authz
import data.common_utils
# Note: package name uses underscores
default allow = false
allow {
common_utils.is_valid(input)
}
Best Practices
- Single responsibility: Each library should have a focused purpose
- Clear naming: Use descriptive package and function names
- Document functions: Add comments explaining what functions do
- Avoid side effects: Libraries should be pure functions
- Test libraries: Write tests for library functions separately
Related Concepts
- Policies: The authorization policies that use libraries
- Policy Domains: Where libraries are defined