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