OPA Policies
The MATIH Platform integrates Open Policy Agent (OPA) for policy-as-code authorization that extends the built-in RBAC model. OPA enables complex, dynamic policies that can consider request context, resource attributes, time of day, data classification levels, and other factors that go beyond simple role-permission mappings. This page documents the OPA integration architecture, policy language, and evaluation flow.
Architecture
OPA runs as a sidecar container alongside each service that requires policy evaluation. The sidecar model ensures low-latency policy decisions without external network calls.
| Component | Description |
|---|---|
| OPA Sidecar | OPA process running in the same pod as the service |
| Policy Bundle | Rego policies loaded from a ConfigMap or policy server |
| Decision Log | Audit trail of all policy decisions |
| Data Store | External data (tenant configs, data classification) synced to OPA |
Deployment Model
# Excerpt from deployment template showing OPA sidecar
containers:
- name: ai-service
image: matih/ai-service:latest
ports:
- containerPort: 8000
- name: opa-sidecar
image: openpolicyagent/opa:latest
args:
- "run"
- "--server"
- "--addr=localhost:8181"
- "--log-level=info"
- "/policies"
volumeMounts:
- name: opa-policies
mountPath: /policiesPolicy Language (Rego)
OPA uses Rego, a high-level declarative language designed for policy authoring. MATIH policies follow a consistent package structure.
Package Convention
All MATIH policies are organized under the matih package namespace:
package matih.authzBase Authorization Policy
The base policy evaluates whether a request should be allowed:
package matih.authz
import future.keywords.if
import future.keywords.in
default allow := false
# Super admin can do anything
allow if {
"super_admin" in input.roles
}
# Tenant admin can manage users within their tenant
allow if {
"tenant_admin" in input.roles
input.resource == "users"
input.action in {"read", "write", "delete"}
input.resource_tenant_id == input.tenant_id
}
# Analysts can read data and execute queries
allow if {
"analyst" in input.roles
input.resource == "data"
input.action == "read"
}
allow if {
"analyst" in input.roles
input.resource == "queries"
input.action in {"read", "write", "execute"}
}Input Schema
Every policy evaluation receives an input document with the following structure:
{
"user_id": "user-123",
"tenant_id": "acme-corp",
"roles": ["analyst", "operator"],
"resource": "queries",
"action": "execute",
"resource_id": "query-456",
"resource_tenant_id": "acme-corp",
"resource_owner_id": "user-123",
"attributes": {
"ip_address": "10.0.1.50",
"mfa_verified": true,
"data_classification": "internal",
"time_of_day": "14:30:00Z"
}
}| Field | Type | Description |
|---|---|---|
user_id | String | Authenticated user ID from JWT |
tenant_id | String | Tenant ID from JWT |
roles | Array | User roles from JWT |
resource | String | Resource type being accessed |
action | String | Action being performed |
resource_id | String | Specific resource identifier |
resource_tenant_id | String | Tenant that owns the resource |
resource_owner_id | String | User that created the resource |
attributes | Object | Additional context attributes |
Advanced Policy Examples
Time-based Access Control
Restrict sensitive operations to business hours:
package matih.authz.time_based
import future.keywords.if
# Allow data export only during business hours (UTC)
allow_export if {
input.action == "export"
time.clock(time.now_ns())[0] >= 8 # After 8 AM
time.clock(time.now_ns())[0] < 18 # Before 6 PM
}
deny_export if {
input.action == "export"
not allow_export
}Data Classification Policies
Enforce access based on data sensitivity levels:
package matih.authz.classification
import future.keywords.if
import future.keywords.in
# Data classification hierarchy
classification_level := {
"public": 0,
"internal": 1,
"confidential": 2,
"restricted": 3,
}
# User clearance based on role
user_clearance := 3 if { "super_admin" in input.roles }
user_clearance := 2 if { "tenant_admin" in input.roles }
user_clearance := 1 if { "analyst" in input.roles }
user_clearance := 0 if { "viewer" in input.roles }
# Allow access if user clearance meets or exceeds data classification
allow if {
data_level := classification_level[input.attributes.data_classification]
user_clearance >= data_level
}Rate Limiting Policy
Policy-based rate limiting based on role and operation:
package matih.authz.rate_limit
import future.keywords.if
# Rate limits per role per minute
rate_limits := {
"super_admin": {"queries": 1000, "exports": 100},
"tenant_admin": {"queries": 500, "exports": 50},
"analyst": {"queries": 100, "exports": 10},
"viewer": {"queries": 50, "exports": 5},
}
# Get the most permissive rate limit from user's roles
max_rate_limit(resource) := limit if {
limits := [l | role := input.roles[_]; l := rate_limits[role][resource]]
limit := max(limits)
}Cross-tenant Data Sharing
Policy for controlled cross-tenant data access:
package matih.authz.sharing
import future.keywords.if
import future.keywords.in
# Allow cross-tenant access if explicitly shared
allow if {
input.resource_tenant_id != input.tenant_id
sharing_agreement_exists(input.tenant_id, input.resource_tenant_id)
input.action == "read" # Only read access for shared data
}
sharing_agreement_exists(requester, owner) if {
agreement := data.sharing_agreements[_]
agreement.requester_tenant == requester
agreement.owner_tenant == owner
agreement.status == "active"
}Policy Testing
OPA includes a built-in testing framework. All MATIH policies include companion test files:
package matih.authz_test
import future.keywords.if
test_super_admin_allowed if {
allow with input as {
"user_id": "admin-1",
"tenant_id": "platform",
"roles": ["super_admin"],
"resource": "users",
"action": "delete",
}
}
test_viewer_cannot_write if {
not allow with input as {
"user_id": "user-1",
"tenant_id": "acme",
"roles": ["viewer"],
"resource": "data",
"action": "write",
}
}
test_tenant_isolation if {
not allow with input as {
"user_id": "user-1",
"tenant_id": "acme",
"roles": ["tenant_admin"],
"resource": "users",
"action": "read",
"resource_tenant_id": "other-tenant",
}
}Run tests with:
opa test ./policies/ -vDecision Logging
OPA decision logs are sent to the audit service for compliance:
| Field | Description |
|---|---|
decision_id | Unique ID for the decision |
timestamp | When the decision was made |
input | The input document (redacted for PII) |
result | The decision result (allow/deny) |
policy | Which policy rule matched |
latency_ms | Decision evaluation time |
Performance
OPA policy evaluation is designed for low latency:
| Metric | Typical Value |
|---|---|
| Simple RBAC check | < 0.1 ms |
| Complex policy with data lookup | 0.5-2 ms |
| Policy with external data sync | 1-5 ms (data pre-loaded) |
| Policy bundle reload | 10-50 ms |
Policies and data are loaded into memory. OPA never makes external network calls during evaluation.
Related Pages
- RBAC Model -- Core RBAC system
- Role Hierarchy -- Role inheritance model
- Permission Types -- Resource and action permissions
- Authorization -- Complete authorization architecture