MATIH Platform is in active MVP development. Documentation reflects current implementation status.
3. Security & Multi-Tenancy
rbac
OPA Policies

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.

ComponentDescription
OPA SidecarOPA process running in the same pod as the service
Policy BundleRego policies loaded from a ConfigMap or policy server
Decision LogAudit trail of all policy decisions
Data StoreExternal 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: /policies

Policy 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.authz

Base 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"
  }
}
FieldTypeDescription
user_idStringAuthenticated user ID from JWT
tenant_idStringTenant ID from JWT
rolesArrayUser roles from JWT
resourceStringResource type being accessed
actionStringAction being performed
resource_idStringSpecific resource identifier
resource_tenant_idStringTenant that owns the resource
resource_owner_idStringUser that created the resource
attributesObjectAdditional 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/ -v

Decision Logging

OPA decision logs are sent to the audit service for compliance:

FieldDescription
decision_idUnique ID for the decision
timestampWhen the decision was made
inputThe input document (redacted for PII)
resultThe decision result (allow/deny)
policyWhich policy rule matched
latency_msDecision evaluation time

Performance

OPA policy evaluation is designed for low latency:

MetricTypical Value
Simple RBAC check< 0.1 ms
Complex policy with data lookup0.5-2 ms
Policy with external data sync1-5 ms (data pre-loaded)
Policy bundle reload10-50 ms

Policies and data are loaded into memory. OPA never makes external network calls during evaluation.


Related Pages