Skip to content
Updated Jun 9, 2026

Access Control & Data Protection

How Olly decides who may see what (ABAC) and how it stores PII so that no single admin can read it at rest (data protection). Two distinct concerns that compose - proven, at our scale, by five reproducible POCs.

Status - 2026-05-27

Design drafted; all 5 de-risking POCs GO. No POC forced an architecture change. Use the ∑ terse / ¶ plain toggle (top-right) to switch paired concepts between the formal statement and a plain-English one.


Two concerns, one system

  1. ABAC - Attribute-Based Access Control. Decision = f(subject_attrs, resource_attrs, action, env). Enforced by OPA (PDP) + an RLS floor (PEP); attributes sourced from Keycloak (PIP).
  2. Data protection at rest - cls(f) ≥ pii stored as AES-256-GCM(DEK), DEK = wrap(KEK), KEK in OpenBao under Shamir k-of-n. Confidentiality vs privileged insiders; not access control.
Plain-English explanation

There are two separate problems here, and conflating them is the usual mistake:

  1. Access control (ABAC). Given who is asking (their role, which broker/scheme they belong to - their "attributes"), what they're touching, and what they're trying to do, decide yes or no. We do this with a policy engine (OPA) backed by a database-level safety net (row-level security). The attributes that drive the decision come from Keycloak, our identity provider.
  2. Data protection at rest. Even someone allowed nowhere near the data - a database admin, someone with server access - should not be able to read members' personal data straight off the disk. That's a cryptography-and-key-custody problem, solved by encrypting the data and splitting the keys so no single person holds them. This is not about permissions; it sits underneath access control.

They meet at exactly one point: a request that passes the access check then asks the vault to decrypt - and the vault still independently enforces key custody and writes an audit record.

ABAC, in standard terms

ABAC roleOlly component
PIP - attribute sourceKeycloak (subject: role, party_locator, org_locator, scheme) + row attributes + request context
PDP - decision pointOPA - one Rego drives point check and list filter (no-drift), plus field-level Φ
PEP - enforcementthe service + the RLS floor at the data tier
PAP - policy adminthe Rego policy repo

Keycloak is the PIP, not the PII store

It manages subject attributes - who the caller is. The members' record PII (names, DOB, NHS#, clinical, financial) lives in the operational DB / vault; ABAC gates access to it, the vault protects it at rest.

Detail: ABAC design · Data protection at rest - each with a block + sequence diagram.

Validation - the POCs ✓ all GO

POCConcernClaim under testVerdictHeadline
#1 OPA partial-evalABACone Rego → point check and SQL filter✓ GOno-drift over 500 actors/role
#2 RLS + pgbouncerABACRLS survives txn pooling✓ GOsafe pattern 0 leaks / 50k; naive SET 99.8%
#3 OpenBao Transitdata-protvault sustains detok QPS✓ GO~404k unwrap/s, 80-1300× target
#4 Composed latencybothRLS+OPA+vault fit budget✓ GO @ anchorp99 9.6-29.7ms @ ~300 req/s
#5 Blind-index searchdata-protsearchable encryption for the search UXGO w/ constraintleakage ∝ 1/cardinality

See POC plan & evidence for thresholds, sequencing, and the directives each verdict produced.

For engineers - how to grant access

New to the codebase and handed "give role R access to table T"? Granting a role access to a table is the week-one walkthrough: a role's access is data ({column, op, actor_field}) read by one shared Rego - you add a rule, not policy code - and the platform validates shape, no-drift, isolation, sargability and fail-closed for you. Worked example, real files, click-to-test in the harness.

Operating it - onboarding a tenant

The same invariants drive operations. Tenant onboarding - test & review flow is a step-by-step of how a new tenant's access configuration (roles, RLS predicate, field policy) is validated and signed off before it serves a request: static validation → automated isolation + no-drift in a sandbox → 4-eyes review (click through the harness) → staged rollout → reversible promote. Nothing promotes past a cross-tenant leak or any drift.

Build rules the POCs produced

  1. RLS context = one statement - SECURITY INVOKER fn + SET LOCAL in the query's txn (never session SET), NULLIF(...) fail-closed, FORCE RLS.
  2. OPA must be sidecar / cached / embedded - a single shared OPA over HTTP is the first thing to saturate (the only layer that broke budget, at 16× peak).
  3. The vault is not a bottleneck - Transit unwrap ~0.7ms; the audit write dominates. No DEK caching (it would reintroduce key exposure).
  4. Partial-match search indexes live in the vault trust domain, gated by Φ - cheap partial-name search and strong name-confidentiality are mutually exclusive on one store.

Open decisions (for review)

  • DEK granularity - per-record vs per-subject (latter unlocks crypto-shredding for erasure).
  • Shamir (n, k) + who holds shares - does separation of duties need a non-engineering custodian?
  • OPA topology - sidecar per service vs shared; decision-cache TTL.
  • Phase B trigger - when to move the KEK behind a hardware HSM.

Olly Health Insurance Platform