Skip to content

OPA — Fine-Grained Access Control

Open Policy Agent (OPA) provides policy-as-code authorization for AKKO. It integrates with Trino to enforce row-level security, column masking, and attribute-based access control (ABAC) — all defined in Rego policies.

How It Works

User (alice) → Trino Query → OPA Policy Check → Allow / Deny / Filter / Mask

OPA evaluates every Trino query against Rego policies before execution:

  1. Authorization — Can this user access this catalog/schema/table?
  2. Row filtering — Which rows should this user see?
  3. Column masking — Should sensitive columns be redacted?

Policy Structure

Policies are defined in Rego and deployed as a ConfigMap (see helm/akko/charts/akko-opa/templates/configmap.yaml). Three policy files handle different aspects:

File Purpose
trino_access.rego Operation-level allow/deny per role
column_masking.rego PII column masking for user/viewer
row_filter.rego Row-level security (e.g., viewers see active accounts only)
package trino
import rego.v1

# Default deny — every access must be explicitly allowed
default allow := false

# Admins can do everything
allow if {
    "akko-admin" in input.context.identity.groups
}

# Standard users (compliance): SELECT only, PII masked (see column_masking.rego)
allow if {
    "akko-user" in input.context.identity.groups
    input.action.operation in [
        "ExecuteQuery", "AccessCatalog", "FilterCatalogs",
        "FilterSchemas", "FilterTables", "FilterColumns",
        "SelectFromColumns", "ShowSchemas", "ShowTables",
        "ShowColumns", "ShowCreateTable", "ShowFunctions", "ShowStats"
    ]
}

RBAC Roles

OPA policies map to five Keycloak RBAC roles:

Role Access Level
akko-admin Full access, no masking
akko-engineer DDL + DML (CREATE, INSERT, SELECT), no masking
akko-analyst SELECT only, full PII visibility (no masking)
akko-user SELECT only, PII columns masked
akko-viewer SELECT only, row-filtered, PII masked

For the full access matrix and policy details, see the RBAC Administration Guide.

Trino Integration

Trino is configured to delegate access control to OPA:

access-control.name=opa
opa.policy.uri=http://akko-akko-opa:8181/v1/data/trino/allow
opa.policy.row-filters-uri=http://akko-akko-opa:8181/v1/data/trino/rowFilters
opa.policy.column-masking-uri=http://akko-akko-opa:8181/v1/data/trino/columnMask

Configuration

Kubernetes (Helm)

akko-opa:
  enabled: true
  resources:
    requests:
      cpu: 50m
      memory: 64Mi
    limits:
      cpu: 200m
      memory: 256Mi

OPA is lightweight — it requires minimal resources and adds negligible latency to queries.

Adding Policies

  1. Create or edit Rego files in the OPA policies directory
  2. Policies are mounted via ConfigMap using subPath per file (not directory mount, to avoid symlink loops)
  3. OPA hot-reloads policies — no restart required

Testing Policies

Use opa eval locally to test policies before deploying:

opa eval -i input.json -d policy.rego "data.trino.allow"

Troubleshooting

Policy Evaluation Denied Unexpectedly

Symptoms: Trino queries fail with Access Denied for users who should have access. The OPA decision log shows "result": false for requests that should be allowed.

Cause: The Rego policy logic is incorrect, the user's group memberships from Keycloak are not being passed correctly in the OPA input, or a more restrictive rule is overriding the default allow = true.

Solution:

# Check OPA decision logs
kubectl logs -n akko deploy/akko-akko-opa --tail=100

# Query OPA directly to debug the decision
kubectl exec -n akko deploy/akko-akko-opa -- curl -s -X POST \
  http://localhost:8181/v1/data/trino/allow \
  -d '{"input":{"context":{"identity":{"user":"alice","groups":["akko-admin"]}},"action":{"operation":"SelectFromColumns"},"resource":{"table":{"catalogName":"iceberg","schemaName":"banking","tableName":"customers"}}}}' | python3 -m json.tool

# Verify the user's groups are correctly mapped from Keycloak
kubectl exec -n akko deploy/akko-trino-coordinator-0 -- \
  curl -s http://localhost:8080/v1/info | python3 -m json.tool

Trino Integration Failure (Bundle Not Loading)

Symptoms: Trino starts but all queries return Access Denied. OPA logs show Bundle download failed or policy not found. The OPA /health endpoint returns unhealthy.

Cause: The policy ConfigMap is not mounted correctly, the Rego files are empty (0-byte due to misconfigured .Files.Get), or OPA cannot parse the policy files due to syntax errors.

Solution:

# Check OPA pod health
kubectl get pods -n akko -l app.kubernetes.io/name=akko-opa

# Verify policy files are mounted and non-empty
kubectl exec -n akko deploy/akko-akko-opa -- ls -la /policies/
kubectl exec -n akko deploy/akko-akko-opa -- cat /policies/trino.rego

# Check OPA logs for policy compilation errors
kubectl logs -n akko deploy/akko-akko-opa --tail=50 | grep -i "error\|compile\|parse\|bundle"

# Verify the ConfigMap contains the policies
kubectl get cm -n akko -l app.kubernetes.io/name=akko-opa -o yaml | head -50

# If files are 0-byte, check the Helm chart uses .Files.Get (not .Values)

Role Mapping from Keycloak JWT

Symptoms: OPA policies that check input.context.identity.groups never match. Users authenticated via Keycloak are treated as having no groups, even though they have roles assigned.

Cause: Trino is not extracting groups from the Keycloak JWT token correctly. The groups claim may be nested under realm_access.roles or resource_access instead of a top-level groups field.

Solution:

# Decode a JWT token to inspect its structure
# First, get a token from Keycloak
kubectl exec -n akko deploy/akko-keycloak -- \
  curl -s -X POST http://localhost:8080/realms/akko/protocol/openid-connect/token \
  -d "client_id=trino&client_secret=<secret>&grant_type=client_credentials" \
  | python3 -c "import sys,json,base64; t=json.load(sys.stdin)['access_token'].split('.')[1]; print(json.dumps(json.loads(base64.urlsafe_b64decode(t+'==')),indent=2))"

# Verify Trino's group extraction config
kubectl exec -n akko deploy/akko-trino-coordinator-0 -- cat /etc/trino/config.properties | grep -i group

# Check OPA input by enabling decision logging
kubectl exec -n akko deploy/akko-akko-opa -- curl -s http://localhost:8181/v1/data/trino/allow \
  -X POST -d '{"input":{"context":{"identity":{"user":"alice","groups":[]}}}}'

Policy Syntax Errors in Rego

Symptoms: OPA pod starts but policies are not loaded. Logs show rego_parse_error or rego_type_error. All Trino queries fail with access denied.

Cause: The Rego policy file contains syntax errors (missing imports, wrong operators, undefined variables, or incompatible future.keywords usage).

Solution:

# Check OPA logs for parse/compile errors
kubectl logs -n akko deploy/akko-akko-opa --tail=50 | grep -i "error\|parse\|compile"

# Validate the policy locally before deploying
kubectl exec -n akko deploy/akko-akko-opa -- opa check /policies/trino.rego

# Test the policy with a sample input
kubectl exec -n akko deploy/akko-akko-opa -- opa eval \
  -d /policies/trino.rego \
  -i /dev/stdin "data.trino.allow" <<< '{"context":{"identity":{"user":"alice","groups":["akko-admin"]}}}'

# If the policy is broken, fix the Rego file in the Helm chart and redeploy
# The ConfigMap will be updated and OPA hot-reloads automatically