Governance — RBAC¶
AKKO enforces a single, platform-wide RBAC model that flows from Keycloak through OPA into every service that touches data. This page is the governance-level summary. For the full operational guide (adding users, Rego examples, API scripts, testing), see Admin / RBAC.
One Model, Four Enforcement Points¶
flowchart LR
KC[Keycloak realm akko<br/>5 realm roles + groups] --> JWT[JWT with groups claim]
JWT --> S1[Service OIDC<br/>Superset, Airflow, Grafana, JupyterHub, MLflow]
JWT --> OPA[OPA<br/>row filters + column masks]
JWT --> LLM[LiteLLM<br/>per-role virtual keys]
OPA --> TR[Trino]
S1 --> TR
LLM --> OLL[Ollama]
Four enforcement points consume the same identity:
- Service OIDC — Superset, Airflow, Grafana, JupyterHub, MLflow map
groupsclaim to their internal roles. - OPA — Trino delegates authorization, row filters and column masking decisions.
- Trino file rules — catalog/schema/table ACLs refreshed every 5 minutes.
- LiteLLM — virtual API keys scoped per role, so model routing and rate limits follow the user.
The Six Effective Roles¶
AKKO ships five realm roles in Keycloak, plus an implicit service-account role for non-human identities. Each role is the authoritative source of truth — mapped automatically to every downstream service.
| Role | Persona | Data visibility | PII | Query power |
|---|---|---|---|---|
akko-admin |
SRE / platform owner (Alice) | all catalogs, all schemas | clear | DDL, DML, SELECT |
akko-engineer |
Data engineer (Bob) | raw, staging, analytics, sandbox |
clear | DDL on designated schemas, INSERT, SELECT |
akko-analyst |
Senior analyst (Carol) | read-only all schemas | clear | SELECT only |
akko-user |
Standard user / compliance (Eve) | read-only all schemas | masked (email, phone, ssn, dob, mrn) | SELECT only |
akko-viewer |
Executive / dashboard viewer (Dave) | analytics, reporting, public |
masked | SELECT, row-filtered |
| service-account | CI jobs, dbt, Airflow DAGs | project-scoped | per project | scoped per token |
Service-Level Mapping¶
| Service | akko-admin |
akko-engineer |
akko-analyst |
akko-user |
akko-viewer |
|---|---|---|---|---|---|
| Trino | DDL + DML | DDL on raw/staging/analytics/sandbox |
SELECT | SELECT + PII mask | SELECT + row filter + PII mask |
| Superset | Admin | Alpha | Gamma | Gamma | Public |
| Airflow | Admin | User | Viewer | Viewer | Viewer |
| Grafana | Admin | Editor | Viewer | Viewer | Viewer |
| JupyterHub | admin panel + spawn | spawn | spawn | spawn | spawn |
| MLflow | full | full | full | full | full |
| LiteLLM | all models, 1000 rpm | all models, 200 rpm | all models, 100 rpm | chat + embed, 50 rpm | chat + embed, 20 rpm |
| OpenMetadata | admin | browse + edit | browse + edit | browse | browse |
| MinIO | root | root | root | root | root |
MinIO, MLflow and Spark still use shared credentials; per-user enforcement is tracked in Stream 19 of the roadmap.
OPA — Row Filters and Column Masks¶
OPA is the authority for row-level security and column masking on top of Trino's file-based ACLs. Policies are hot-reloaded from a ConfigMap, no pod restart.
# package trino — excerpt
default allow := false
allow if { "akko-admin" in input.context.identity.groups }
# akko-user: SELECT-only
allow if {
"akko-user" in input.context.identity.groups
input.action.operation in {
"ExecuteQuery","AccessCatalog","FilterCatalogs","FilterSchemas",
"FilterTables","FilterColumns","SelectFromColumns",
"ShowSchemas","ShowTables","ShowColumns","ShowStats"
}
}
# Column masking for PII — applies to akko-user and akko-viewer
columnMask := {"expression": "'***MASKED***'", "identity": "mask_pii"} if {
input.action.resource.column.columnName in {"email","phone","ssn","medical_record_number"}
not "akko-admin" in input.context.identity.groups
not "akko-engineer" in input.context.identity.groups
not "akko-analyst" in input.context.identity.groups
}
# Row filter: viewers see only active accounts
rowFilters contains {"expression": "status = 'active'", "identity": "viewer_active_only"} if {
"akko-viewer" in input.context.identity.groups
input.action.resource.table.tableName == "accounts"
}
PII Tag Sync from OpenMetadata¶
A sidecar (opa-sync) polls OpenMetadata every 5 minutes and pushes PII column tags as OPA data. New columns tagged PII are masked automatically without a policy redeploy. See Security Flow.
LLM RBAC¶
LiteLLM enforces per-role virtual keys, routing and rate limits. See Admin / LLM RBAC for the full matrix.
| Role | Models allowed | Rate limit | Max context |
|---|---|---|---|
akko-admin |
all | 1000 rpm | 128 k |
akko-engineer |
all | 200 rpm | 64 k |
akko-analyst |
chat + embed | 100 rpm | 32 k |
akko-user |
chat + embed | 50 rpm | 16 k |
akko-viewer |
chat only | 20 rpm | 8 k |
Trino AI functions — per-UDF enforcement¶
The 17 ai_* UDFs exposed by the Trino plugin bypass Trino's native OPA
ExecuteFunction check (plugin UDFs don't flow through the access-control
SPI). RBAC is therefore enforced at the AI Service layer:
- The Trino plugin forwards
X-Trino-User+X-Akko-Ai-Function: ai_<name>on every HTTP call. - AI Service resolves the forwarded user's realm role via the Keycloak Admin API (cached 5 min) and checks the per-function matrix.
- Denials return HTTP 403 → SQL
NULL(fail-closed); every decision is logged to Loki withaudit_type=AI_RBACand counted in Prometheus.
Full 17×5 matrix: see AI / Trino functions.
Source of truth: helm/akko/charts/akko-ai-service/values.yaml, aligned
with helm/akko/charts/akko-opa/templates/configmap.yaml.
Enterprise Federation¶
When global.auth.mode=enterprise, the seed demo users are disabled and Keycloak federates either:
- a corporate LDAP (LLDAP or external),
- or an upstream OIDC provider (Azure AD, Okta, Google Workspace, KeyCloak, Authentik),
- or SAML (ADFS, PingFederate).
See Admin / External Identity Provider and Admin / Enterprise User Federation.
Audit Trail¶
Three log streams land in Grafana for forensic review:
| Source | Format | Examples |
|---|---|---|
| Keycloak events | JSON via keycloak-event-listener-http |
LOGIN, LOGOUT, UPDATE_PASSWORD, TOKEN_REFRESH |
| OPA decision log | JSON console decision log | {user, input, result, policy} |
| Trino query log | JSON event listener | {queryId, user, sql, bytes_scanned, duration} |
| ADEN audit_log | JSON to Loki | aden_query_received, aden_opa_denied, aden_pii_masked |
Troubleshooting (Top 5)¶
| Symptom | Check | Fix |
|---|---|---|
| User sees no data in Superset | groups claim in JWT, Superset role mapping |
Re-login; verify Keycloak role assignment |
| Column still visible in clear | Role is admin/engineer/analyst |
Expected — masking only applies to user/viewer |
| OAuth2-Proxy rejects user | emailVerified is false |
Enable in Keycloak admin UI |
| Airflow / Grafana role stuck after change | Token cached 5 min | User must log out and back in |
| New OPA rule not picked up | ConfigMap not synced | kubectl rollout restart deploy/akko-akko-opa (rare) or wait 30 s |
Related¶
- Admin / RBAC — full operational guide, API scripts, testing
- Admin / Governance Architecture
- Admin / LLM RBAC
- Admin / External Identity Provider
- Architecture / Security Flow