Skip to main content
  1. System Design Components/

RBAC / ABAC Policy Engine

RBAC / ABAC Policy Engine #

This note models an RBAC/ABAC policy engine where callers ask whether a subject may perform an action on a resource, admins update roles, bindings, attributes, and policies, and the system evaluates decisions consistently at scale.


Step 1 - Normalize #

Assume the baseline prompt is:

  • design an RBAC / ABAC policy engine
  • callers ask authorization questions like can user U do action A on resource R
  • policies can reference roles, bindings, subject/resource attributes, and contextual attributes
  • admins update policies and role bindings over time
  • policy evaluation must be fast and correct across many services and tenants

Normalize into state-affecting paths.

RequirementActorOperationState touchedPriority
Caller evaluates authorization decisionClientread sourceS1
read source target
PolicyState
C1
Admin updates policy definitionAdminoverwrite stateS1
update target
PolicyState
C1
Admin updates role definitionAdminoverwrite stateS1
update target
RoleDefinition
C1
Admin grants or revokes role bindingAdminstate transitionS1
update target
RoleBindingState
C1
System updates subject attributesSystemoverwrite stateS1
update target
SubjectAttributeState
C1
System updates resource attributesSystemoverwrite stateS1
update target
ResourceAttributeState
C1
System computes or refreshes effective policy snapshotSystemstate transitionS1
update target
PolicySnapshot
C1
System propagates policy snapshot to evaluatorsSystemasync processS1
hidden write target
EvaluatorConfigSnapshot
C1
User reads audit / access activityClientread projectionS1
read projection target
AuthorizationAuditView
R2
System routes tenant/shard to current ownerSystemread sourceS1
read source target
PartitionMap
C1
System reassigns shard ownership after node failureSystemstate transitionS1
update target
PartitionOwnership
C1

Notes on normalization #

Important choices:

  • authorization evaluation is a hot read path
  • policy and role definitions are current-value control state
  • role grants/revokes are lifecycle transitions
  • attribute updates are current-value state
  • snapshot generation/propagation is explicit because evaluators usually should not hit the control plane on every request

This system is a hybrid of:

  • policy and relationship state
  • hot-path evaluation
  • control plane to evaluator snapshot distribution

Step 2 - Critical Path Selection #

RequirementPriority classWhy
Evaluate authorization decisionC1wrong allow/deny is the core correctness failure
Update policy definitionC1changes future authorization decisions
Update role definitionC1changes derived permissions
Grant / revoke role bindingC1changes subject access immediately or near-immediately
Update subject attributesC1ABAC conditions depend on current subject properties
Update resource attributesC1ABAC conditions depend on current resource properties
Compute effective policy snapshotC1hot-path evaluators depend on correct derived state
Propagate snapshot to evaluatorsC1stale evaluators can enforce wrong permissions
Read audit / activityR2operational/compliance only
Route to shard ownerC1wrong routing can split policy truth
Reassign shard ownershipC1failover must preserve policy/state correctness

Baseline critical paths #

Main C1 paths:

  • P1 evaluate authorization
  • P2 update policy
  • P3 update role definition
  • P4 grant/revoke binding
  • P5 update subject attributes
  • P6 update resource attributes
  • P7 compute policy snapshot
  • P8 propagate evaluator snapshot
  • P9 route to shard owner
  • P10 reassign shard ownership

This design is driven by:

  • authoritative current policy, bindings, and attributes
  • fast evaluator reads
  • bounded-stale but monotonic config distribution

Step 3 - Primary State Extraction #

For an RBAC/ABAC engine, the minimal primary state is policy definitions, role definitions, role-binding lifecycle, subject/resource attributes, derived policy snapshot state, and routing/ownership state.

Candidate object labelCandidate sourceCandidate needed for C1/R1?Candidate decomposition actionClassPrimary?OwnerEvolutionScope kindScope value
PolicyStatedirect nounYeskeep as candidateentityYesserviceoverwriteinstancetenant_id or policy_scope
RoleDefinitiondirect nounYeskeep as candidateentityYesserviceoverwriteinstancerole_id
RoleBindingStatelifecycle objectYeskeep as candidateprocessYesservicestate machineinstancesubject_id + role_id + scope
SubjectAttributeStatedirect nounYeskeep as candidateentityYesserviceoverwriteinstancesubject_id
ResourceAttributeStatedirect nounYeskeep as candidateentityYesserviceoverwriteinstanceresource_id
PolicySnapshothidden write targetYeskeep as candidateprocessYesserviceoverwriteinstancetenant_id or evaluator_scope
EvaluatorConfigSnapshothidden write targetYeskeep as candidateprojectionYesserviceoverwriteinstanceevaluator_id
PartitionOwnershiphidden write targetYeskeep as candidateprocessYesservicestate machineinstanceshard_id
PartitionMaphidden write targetYeskeep as candidateentityYesserviceoverwritecollectiontenant/shard map
AuthorizationAuditViewderived read modelNoreject as UI artifactprojectionNoderivedoverwritecollectiontenant or subject

Important modeling choices #

PolicyState #

Primary because:

  • policy language/rules are authoritative control truth

RoleBindingState #

Primary because:

  • grants and revokes are not just facts; current active/inactive lifecycle matters

SubjectAttributeState / ResourceAttributeState #

Primary because:

  • ABAC decisions depend on current attributes

PolicySnapshot #

Primary because:

  • many production engines derive an effective evaluation model from raw policies, roles, and bindings

EvaluatorConfigSnapshot #

Kept explicit because:

  • hot-path evaluators often run from local snapshots, not synchronous control-plane reads

Minimal strict primary set #

The strongest minimal set is:

  • PolicyState
  • RoleDefinition
  • RoleBindingState
  • SubjectAttributeState
  • ResourceAttributeState
  • PolicySnapshot
  • EvaluatorConfigSnapshot
  • PartitionOwnership
  • PartitionMap

Step 4 - Hard Invariants #

For an RBAC/ABAC policy engine, the hard invariants are about one authoritative current policy/binding/attribute state, correct derived snapshots, and authorization decisions being evaluated against the intended current version.

PathTierTypeInvariant statement
P1 evaluate authorizationHARDeligibilityDecision authorize(subject, action, resource, context) is valid only if it is evaluated against current authoritative or approved snapshot versions of PolicyState, RoleBindingState, SubjectAttributeState, and ResourceAttributeState for the request scope.
P2 update policyHARDorderingPolicy revisions are ordered by monotonic version within policy scope.
P3 update role definitionHARDorderingRole-definition revisions are ordered by monotonic version within role scope.
P4 grant / revoke bindingHARDuniquenessKey (subject_id, role_id, scope) maps to at most one logical outcome current authoritative binding state within binding scope.
P5 update subject attributesHARDorderingSubject-attribute revisions are ordered by monotonic version within subject scope.
P6 update resource attributesHARDorderingResource-attribute revisions are ordered by monotonic version within resource scope.
P7 compute policy snapshotHARDaccountingPolicySnapshot equals the deterministic function of current policy, roles, bindings, and attribute schema/input state for its evaluation scope.
P8 propagate evaluator snapshotHARDfreshnessEvaluatorConfigSnapshot(evaluator_id) reflects an authoritative PolicySnapshot within configured propagation bounds and moves monotonically forward by version.
P9 route to shard ownerHARDuniquenessKey shard_id maps to at most one logical outcome current authoritative owner within shard_id.
P10 reassign shard ownershipHARDeligibilityAction reassign_shard is valid only if current owner is failed or relinquished and candidate owner is eligible and sufficiently current on shard_id at decision time.

What matters most #

1. One authoritative current binding state #

Role grants and revokes must not race into conflicting current truth.

2. Snapshot correctness #

Evaluators can be local and fast, but their snapshot must correspond to real policy truth.

3. Monotonic evaluator config #

An evaluator must not move backward to an older policy version.

4. Freshness is a deliberate tradeoff #

If bounded-stale local evaluation is allowed, that must be explicit.


Step 5 - Execution Context #

For the baseline RBAC/ABAC policy engine:

FieldValueWhy
Topologysingle service distributedone logical authorization-policy system with control plane and evaluator fleet
Write coordination scopeper object scopecorrectness is per policy, role, binding, attribute, and shard ownership scope
Read consistency targetbounded stale allowedhot-path evaluation often uses local snapshots with strict version discipline
Holder modelnoneno lease-like client ownership is central to per-decision correctness
Compensation acceptable?Nowrong allow/deny decisions cannot be repaired afterward

Derived implications #

  • holder_may_crash = false

    • evaluators can fail, but they do not hold mutable business ownership like queue workers
  • cross_service_write = false

    • baseline keeps policy, bindings, attributes, and snapshots in one logical service
  • bounded_staleness_allowed = true

    • hot-path local evaluation can tolerate bounded lag if explicit
  • cross_service_atomicity_required = false

    • no multi-service transaction across unrelated services in baseline
  • exclusive_claim_required = true

    • shard ownership must be exclusive
  • guarded_by_current_state = true

    • binding grants/revokes and snapshot updates depend on current state

What this implies #

This pushes us toward:

  • one authoritative owner per tenant/policy shard
  • current-value policy and attribute state
  • local evaluator snapshots distributed from control plane
  • monotonic versioned evaluation

Step 6 - Deterministic Mechanism Selection #

PathWrite shapeBase mechanismRequired companions
P1 evaluate authorizationread sourcedirect source read or local snapshot readsnapshot version
P2 update policyoverwrite current valueCAS on versionpolicy version
P3 update role definitionoverwrite current valueCAS on versionrole version
P4 grant / revoke bindingguarded state transitionCAS on (state, version)binding version
P5 update subject attributesoverwrite current valueCAS on versionattribute version
P6 update resource attributesoverwrite current valueCAS on versionattribute version
P7 compute policy snapshotoverwrite current valuesingle writer control-plane recomputesnapshot version
P8 propagate evaluator snapshotoverwrite current valuesingle writer snapshot publicationconfig version
P9 route to shard ownerexclusive claimleasefencing token, heartbeat
P10 reassign shard ownershipguarded state transitionCAS on (state, version)fencing token, shard catch-up check

Why these fit #

Policies, roles, and attributes #

These are current-value control state, so overwrite fits.

Role bindings #

Bindings have lifecycle and current-state transitions, so guarded transition fits.

Snapshot build and propagation #

These are derived current views published to evaluators, so overwrite fits.

Routing #

One owner per shard is required for correctness of authoritative updates, so exclusive claim fits.

Canonical substrate implied #

The baseline now points to:

  • sharded policy-control service
  • one owner per tenant or policy shard
  • current policy, binding, and attribute state
  • derived snapshots pushed to evaluators
  • bounded-stale but monotonic local decisions

Step 7 - Read Model / Source of Truth #

For an RBAC/ABAC engine, truth is mostly direct source state plus derived evaluator snapshots. Audit views are derived.

ConceptTruthRead pathRebuild path
C1 policy definitionsPolicyStateread source directlyauthoritative policy store
C2 role definitionsRoleDefinitionread source directlyauthoritative role store
C3 role-binding lifecycleRoleBindingStateread source directlyauthoritative binding store
C4 subject attributesSubjectAttributeStateread source directlyauthoritative subject-attribute store
C5 resource attributesResourceAttributeStateread source directlyauthoritative resource-attribute store
C6 derived effective policyPolicySnapshotread source directlyrecompute from policy, roles, bindings, and attributes
C7 local evaluator configEvaluatorConfigSnapshotmaterialized viewrebuild from latest PolicySnapshot
C8 shard ownershipPartitionOwnershipread source directlyauthoritative ownership store
C9 shard routing mapPartitionMapread source directlyauthoritative routing metadata
C10 audit / access activityderived from decisions and config statematerialized viewrecompute from authoritative state and decision logs

Important point #

For the core semantics:

  • authoritative truth lives in policy, binding, and attribute state
  • evaluators usually read local snapshots for the hot path
  • audit views are projections

Step 8 - Failure Handling #

PathRetryCompeting writersCrash after commitPublish failureStale holder
P2 update policyretry with policy versionstale update loses CAScommitted policy survives crash if persistedsnapshot propagation may lagstale shard owner blocked by fencing token
P3 update role definitionretry with role versionstale update loses CAScommitted role survives crash if persistedsnapshot propagation may lagstale shard owner blocked by fencing token
P4 grant / revoke bindingretry with binding versionstale grant/revoke loses guarded transitioncommitted binding state survives crash if persistedsnapshot propagation may lagstale shard owner blocked by fencing token
P5 update subject attributesretry with attribute versionstale update loses CAScommitted subject attributes survive crash if persistedsnapshot propagation may lagstale shard owner blocked by fencing token
P6 update resource attributesretry with attribute versionstale update loses CAScommitted resource attributes survive crash if persistedsnapshot propagation may lagstale shard owner blocked by fencing token
P7 compute policy snapshotrecompute retry safe from source inputssingle recompute/version winsrecompute reruns after crashevaluator snapshot may lagn/a
P8 propagate evaluator snapshotretry with versioned snapshotolder snapshot loses to newer versionevaluator keeps last good snapshot until refreshfailed push retried or pulledn/a
P1 evaluate authorizationrequest retry safemany evaluators can answer concurrently from same snapshotevaluator crash drops request onlyn/astale decision bounded by configured snapshot freshness
P9 route to shard ownerretry after refreshing shard maponly one valid owner should existif owner changed, refreshed map points to new ownern/astale owner rejected by fencing token
P10 reassign shard ownershipretry failover transition safelyonly one reassignment wins current ownership statepromoted owner crash triggers later reassignmentn/aold owner fenced and must not continue serving

What matters most #

1. Snapshot monotonicity #

Evaluators must not accept older config after newer config is installed.

2. Freshness versus latency #

The main architectural tradeoff is:

  • direct source read for every authorization
  • versus local snapshot with bounded lag

3. Binding lifecycle correctness #

Revokes must not be lost behind stale grants.

4. Decision logs are optional for correctness #

Audit logging matters for compliance, but decision correctness depends first on policy truth and evaluator freshness.


Step 9 - Scale Adjustments #

HotspotTypeFirst response
very high authorization QPSread hotspotpush local snapshots to evaluators and keep hot path in-memory
hot tenants with many policy updatesfan-out hotspotshard by tenant and batch snapshot recomputes
large subject-resource graph / many bindingsmemory hotspotscope bindings, compress snapshots, and isolate large tenants
expensive ABAC attribute fetchesread hotspotmaterialize needed attributes into policy snapshots or side caches
audit-query loadread hotspotserve from projections and logs, not evaluator hot path
snapshot churncontention hotspotincremental recompute and per-scope propagation

What scales well #

This system scales by:

  • sharding policy truth by tenant/scope
  • evaluating from local snapshots
  • limiting dynamic attribute fetches on the hot path
  • incrementally recomputing only affected policy scopes

What fails first #

Usually:

  • very large binding graphs
  • high-frequency policy churn
  • attribute fetches embedded in every auth decision
  • overly broad global snapshots

Canonical design conclusion #

The mechanical outcome is:

  • primary state:
    • PolicyState
    • RoleDefinition
    • RoleBindingState
    • SubjectAttributeState
    • ResourceAttributeState
    • PolicySnapshot
    • EvaluatorConfigSnapshot
    • PartitionOwnership
    • PartitionMap
  • critical invariants:
    • one authoritative current policy/binding/attribute state
    • correct derived snapshots for evaluator scopes
    • monotonic evaluator config propagation
    • authorization decisions evaluated against intended snapshot/source version
    • exclusive shard ownership for policy truth
  • mechanisms:
    • overwrite current value for policy/roles/attributes
    • guarded transitions for binding lifecycle
    • snapshot recompute and versioned propagation
    • fenced shard ownership
  • reads:
    • hot path from authoritative or approved local snapshot
    • projections for audit and access activity

Polished interview answer #

I’d build the RBAC/ABAC engine as a sharded policy-control service with local evaluator snapshots. The source of truth is current policy definitions, role definitions, role bindings, and subject/resource attributes. Control-plane owners recompute an effective policy snapshot for each tenant or evaluator scope and propagate those snapshots monotonically to evaluators, so the hot authorization path can run in memory without querying the control plane on every request. Grants and revokes are guarded binding-state transitions, while policy and attribute updates are versioned overwrites. The main scaling levers are more tenant shards, incremental snapshot recompute, tight bounds on dynamic attribute lookups, and keeping audit views off the decision hot path.


Concrete Substrate #

I’ll choose a control-plane/data-plane authorization system with authoritative policy shards plus local evaluator snapshots as the concrete baseline, because it matches the mechanics we derived:

  • current-value policy, role, binding, and attribute state
  • derived policy snapshots
  • monotonic snapshot publication to evaluators
  • one owner per shard

Concrete tech family:

  • control plane in Go or Java
  • authoritative state store:
    • replicated DB or RocksDB-backed service state
  • metadata/control:
    • etcd or internal metadata quorum for shard ownership/routing
  • evaluator sidecars/libraries or central PDP fleet using in-memory snapshots

Each shard owner stores:

  • current policies
  • current roles and bindings
  • current subject/resource attributes
  • latest PolicySnapshot per scope

Evaluators store:

  • in-memory EvaluatorConfigSnapshot
  • optional decision cache keyed by (subject, action, resource, context hash, snapshot version)

Operation Layer #

1. Update role binding #

API

  • PutRoleBinding(subject_id, role_id, scope, desired_state, expected_version?)

Initiator

  • admin

Entry point

  • policy API

Authoritative decider

  • shard owner for policy/binding scope

Precondition

  • current binding version matches if optimistic concurrency used

Transition

  • guarded update of RoleBindingState
  • trigger affected snapshot recompute

2. Update policy #

API

  • PutPolicy(scope, policy_doc, expected_version?)

Initiator

  • admin

Entry point

  • policy API

Authoritative decider

  • shard owner for policy scope

Precondition

  • policy version matches if optimistic concurrency used

Transition

  • overwrite PolicyState
  • trigger affected snapshot recompute

3. Evaluate authorization #

API

  • Authorize(subject, action, resource, context)

Initiator

  • client/service

Entry point

  • evaluator / PDP

Authoritative decider

  • local EvaluatorConfigSnapshot, or authoritative policy shard for strong mode

Precondition

  • evaluator snapshot version valid for tenant/scope

Transition

  • none on source truth

Response

  • {allow|deny, reason, snapshot_version}

4. Recompute snapshot #

API

  • internal control-plane recompute flow

Initiator

  • system

Entry point

  • shard owner

Authoritative decider

  • shard owner

Precondition

  • source policy/role/binding/attribute state changed

Transition

  • recompute PolicySnapshot
  • bump version

5. Propagate snapshot #

API

  • internal snapshot push/pull

Initiator

  • system

Entry point

  • control plane / evaluator

Authoritative decider

  • control plane snapshot publisher

Precondition

  • newer PolicySnapshot version exists

Transition

  • overwrite EvaluatorConfigSnapshot

Entry Point vs Decider vs Responder #

PathEntry pointAuthoritative deciderPhysical responderLogical responder
update policy / bindingpolicy APIpolicy shard ownerAPI nodepolicy engine
evaluate authorizationevaluator / PDPlocal evaluator snapshot or source shardevaluator nodepolicy engine
recompute snapshotshard ownershard ownercontrol-plane nodepolicy engine
propagate snapshotcontrol plane / evaluatorsnapshot publishercontrol/data-plane nodepolicy engine
shard failoverfollower / coordination layershard quorum / lease storenew leader / control planepolicy engine

Concrete HLD #

Main components:

  • policy control-plane API
    • receives policy, role, binding, and attribute updates
  • policy shard owners
    • authoritative owners for policy truth and snapshot recompute
  • evaluator fleet or sidecars
    • answer hot authorization queries from local snapshots
  • metadata/control service
    • tracks shard ownership and routing
  • audit/activity pipeline
    • serves decision logs and compliance views

Short Interview Version #

I’d build the RBAC/ABAC engine as a sharded policy-control service with local evaluator snapshots. The source of truth is current policy definitions, role definitions, role bindings, and subject/resource attributes. Control-plane owners recompute an effective policy snapshot for each tenant or evaluator scope and propagate those snapshots monotonically to evaluators, so the hot authorization path can run in memory without querying the control plane on every request. Grants and revokes are guarded binding-state transitions, while policy and attribute updates are versioned overwrites. The main scaling levers are more tenant shards, incremental snapshot recompute, tight bounds on dynamic attribute lookups, and keeping audit views off the decision hot path.