<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>ABAC on Matt Goodrich</title><link>https://mattgoodrich.com/tags/abac/</link><description>Recent content in ABAC on Matt Goodrich</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Fri, 19 Jun 2026 00:01:00 -0700</lastBuildDate><atom:link href="https://mattgoodrich.com/tags/abac/index.xml" rel="self" type="application/rss+xml"/><item><title>Roles Don't Scale the Way You Think</title><link>https://mattgoodrich.com/posts/roles-dont-scale-the-way-you-think/</link><pubDate>Fri, 19 Jun 2026 00:01:00 -0700</pubDate><guid>https://mattgoodrich.com/posts/roles-dont-scale-the-way-you-think/</guid><description>&lt;img src="https://mattgoodrich.com/posts/roles-dont-scale-the-way-you-think/header.png" alt="Featured image of post Roles Don't Scale the Way You Think" />&lt;p>The pitch for role-based access control is clean and convincing. Define what a back-end engineer gets once, attach it to a role, and every back-end engineer inherits it. No more negotiating access per hire, no more guessing what the last person in this job had. You model the org once and access follows the model.&lt;/p>
&lt;p>Then reality adds an exception, and another, and the model starts to fracture. The back-end engineer who also needs production read access. The one on the on-call rotation. The one on the payments team who needs the extra system. Each exception becomes a new role, because a role is the only tool the model gives you, and a few years later you have more roles than employees and no one who can say what half of them are for. Roles do scale. They just scale in the wrong direction.&lt;/p>
&lt;h2 id="what-roles-were-supposed-to-do">What Roles Were Supposed to Do
&lt;/h2>&lt;p>The problem RBAC solves is real, and worth keeping in view while we talk about where it breaks. Before roles, access is negotiated per person: a new hire gets whatever someone remembered to grant, usually by copying the access of whoever sat near them, which copies the last person&amp;rsquo;s mistakes along with it. There is no definition of correct, so there is no way to be correct.&lt;/p>
&lt;p>A role gives you that definition. &lt;a class="link" href="https://csrc.nist.gov/projects/role-based-access-control" target="_blank" rel="noopener"
>NIST formalized RBAC&lt;/a> decades ago around a simple idea: put permissions on roles, put people in roles, and you can finally answer what a given job is supposed to have. For the stable core of an organization, this works exactly as advertised. Everyone in customer support gets the support tools. Everyone in finance gets the finance suite. Define it once, attach it by job, done.&lt;/p>
&lt;p>The model holds as long as access is a function of the job. It breaks where access is a function of anything else.&lt;/p>
&lt;h2 id="role-explosion">Role Explosion
&lt;/h2>&lt;p>Access is rarely a clean function of the job title, and every dimension that the title does not capture becomes a source of new roles.&lt;/p>
&lt;p>Start with one role: &lt;code>engineer&lt;/code>. Some engineers need production read, so now there is &lt;code>engineer&lt;/code> and &lt;code>engineer-prod-read&lt;/code>. Production access varies by team, so each of those splits per team. On-call engineers need elevation, so each team&amp;rsquo;s set doubles again. Add a contractor variant that excludes a few systems, add a region for data-residency reasons, and the single role has become dozens. Repeat across every department and the count runs into the thousands.&lt;/p>
&lt;p>This is &lt;strong>role explosion&lt;/strong>, and its signature is an organization with more roles than people. When that happens, the model has stopped doing its job. The point of roles was to make access legible, to let someone look at a role and know what it means. Ten thousand roles are not legible. Nobody audits them, nobody prunes them, and access certifications turn into rubber-stamping lists of role names whose meaning no one remembers. You have recreated the per-person mess you started with, dressed up as governance.&lt;/p>
&lt;p>&lt;img src="https://mattgoodrich.com/posts/roles-dont-scale-the-way-you-think/diagram-role-explosion.png"
width="1568"
height="174"
srcset="https://mattgoodrich.com/posts/roles-dont-scale-the-way-you-think/diagram-role-explosion_hu437392915452078360.png 480w, https://mattgoodrich.com/posts/roles-dont-scale-the-way-you-think/diagram-role-explosion_hu2547157310113706991.png 1024w"
loading="lazy"
alt="Role Explosion: One Job, Engineer, Multiplied by Each Independent Dimension, Production Access, Team, On-Call, and Contractor, Becomes Dozens of Roles, and Repeated Across Departments Produces More Roles Than the Company Has People"
class="gallery-image"
data-flex-grow="901"
data-flex-basis="2162px"
>&lt;/p>
&lt;p>The mistake underneath role explosion is treating every distinct combination of access as something that needs its own role. Most of those combinations are not jobs. They are the intersection of a job with an attribute, and attributes are a different tool.&lt;/p>
&lt;h2 id="role-based-vs-attribute-based">Role-Based vs Attribute-Based
&lt;/h2>&lt;p>Two models answer the access question in different ways, and the design skill is knowing which to use for which dimension.&lt;/p>
&lt;p>&lt;strong>Role-based&lt;/strong> access derives permissions from role membership, decided ahead of time. You are in the &lt;code>finance-analyst&lt;/code> role, so you get the finance-analyst permissions. It is static, readable, and easy to audit, because the grant is a fact you can look up.&lt;/p>
&lt;p>&lt;strong>Attribute-based&lt;/strong> access derives permissions from attributes evaluated at the moment of the request. &lt;a class="link" href="https://csrc.nist.gov/pubs/sp/800/162/final" target="_blank" rel="noopener"
>NIST&amp;rsquo;s model for ABAC&lt;/a> describes it as a policy that considers attributes of the user, the resource, and the context together: grant access when the user&amp;rsquo;s department equals the resource&amp;rsquo;s owning department, when the request comes from a managed device, when the data&amp;rsquo;s region matches the user&amp;rsquo;s. The decision is computed, not pre-assigned.&lt;/p>
&lt;p>&lt;img src="https://mattgoodrich.com/posts/roles-dont-scale-the-way-you-think/diagram-rbac-vs-abac.png"
width="1568"
height="680"
srcset="https://mattgoodrich.com/posts/roles-dont-scale-the-way-you-think/diagram-rbac-vs-abac_hu3335799043468366566.png 480w, https://mattgoodrich.com/posts/roles-dont-scale-the-way-you-think/diagram-rbac-vs-abac_hu6421945558605615271.png 1024w"
loading="lazy"
alt="Role-Based vs Attribute-Based Access: With RBAC a Users Role Membership Is Decided Ahead of Time and Maps to a Fixed Set of Permissions You Can Look Up; With ABAC the User, Resource, and Context Attributes Are Evaluated by a Policy at the Moment of the Request to Compute an Allow or Deny"
class="gallery-image"
data-flex-grow="230"
data-flex-basis="553px"
>&lt;/p>
&lt;p>The power of attributes is that they collapse the combinations that explode roles. Instead of &lt;code>engineer-team-payments-prod-read&lt;/code> as a named role, you write one policy: an engineer may read production for the team they belong to. The team is an attribute, not a role, so one rule covers every team at once. &lt;a class="link" href="https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction_attribute-based-access-control.html" target="_blank" rel="noopener"
>AWS calls this tag-based access control&lt;/a>: tag the resource with its team, tag the principal with theirs, and a single policy grants access when the tags match, no matter how many teams exist. The roles that would have exploded become attribute values that cost nothing to add. If you are enforcing this in your own services rather than a cloud IAM, &lt;a class="link" href="https://casbin.org/" target="_blank" rel="noopener"
>Casbin&lt;/a> is an open-source library that implements both RBAC and ABAC, and &lt;a class="link" href="https://www.openpolicyagent.org/" target="_blank" rel="noopener"
>OPA&lt;/a> evaluates attribute policies as a standalone engine.&lt;/p>
&lt;h2 id="the-model-that-survives-is-hybrid">The Model That Survives Is Hybrid
&lt;/h2>&lt;p>The workable design is a hybrid. A small number of coarse roles carry the stable, everyone-in-this-job baseline, while attributes handle the dimensions that vary and would otherwise multiply the roles.&lt;/p>
&lt;p>Keep the roles broad and few. &lt;code>engineer&lt;/code>, &lt;code>support&lt;/code>, &lt;code>finance-analyst&lt;/code>, the dozen or so real jobs your company has. These define &lt;a class="link" href="https://mattgoodrich.com/posts/automate-the-leaver-before-the-joiner/" >birthright access&lt;/a>, the baseline everyone in the role gets automatically. Resist the urge to encode every variation as a sub-role.&lt;/p>
&lt;p>Push the variation into attributes. Team, region, device posture, environment, data sensitivity: these are the things that made roles explode, and they are exactly what attribute policies handle in one rule each. The combinatorial mess becomes a handful of policies that read like sentences.&lt;/p>
&lt;p>And leave the genuinely individual, high-sensitivity access to the &lt;strong>requested&lt;/strong> path: asked for, approved by an owner, granted for a reason, and reviewed. Not everything should be automatic. Access to the finance system should cost a human decision, not fall out of a role or a policy.&lt;/p>
&lt;p>The requested path still leaves a standing grant once the request is approved, and for the most dangerous access that is more than you want. Production admin rights and break-glass paths are better with no standing grant at all: &lt;a class="link" href="https://mattgoodrich.com/posts/authorization-broker-models/" >just-in-time elevation through an authorization broker&lt;/a> hands out the access only for the window it is needed and revokes it automatically, so there is nothing left standing to steal, certify, or forget to remove. A role you hold is a target; access you have only for the ten minutes of an incident is a much smaller one.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Dimension&lt;/th>
&lt;th>Right tool&lt;/th>
&lt;th>Why&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>The stable job baseline&lt;/td>
&lt;td>A coarse role&lt;/td>
&lt;td>Same for everyone in the job, easy to read and audit&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Team, region, environment, device&lt;/td>
&lt;td>An attribute policy&lt;/td>
&lt;td>Varies per person; one rule beats one role per value&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>High-sensitivity, individual access&lt;/td>
&lt;td>A request with approval&lt;/td>
&lt;td>Deserves a human decision and a reason, not a default&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>The most dangerous access&lt;/td>
&lt;td>Just-in-time via a broker&lt;/td>
&lt;td>No standing grant to steal or certify; it expires on its own&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>That table is the whole design. Most teams get it wrong by forcing all of it into the first row, modeling every team and every variation as another role, until the role catalog is the problem it was meant to solve.&lt;/p>
&lt;h2 id="attributes-move-the-complexity-they-do-not-delete-it">Attributes Move the Complexity, They Do Not Delete It
&lt;/h2>&lt;p>It would be tidy to say attributes fix everything, and they do not. ABAC trades one hard problem for another, and you should choose it with the trade in view.&lt;/p>
&lt;p>Roles are easy to audit and hard to scale. A role grant is a static fact, so you can list who has a role and reason about it directly, which is why auditors like them. They just multiply badly.&lt;/p>
&lt;p>Attributes scale well and are hard to audit. One policy covers a thousand teams, but answering who can actually reach a given resource now means evaluating a policy against every user&amp;rsquo;s attributes, not reading a list. The complexity did not disappear when you moved it off the roles. It moved into the policies and into the attribute data those policies depend on, and that data has to be clean, current, and trustworthy or the whole thing grants wrong. An ABAC policy keyed on department is only as correct as the department field in your HR system. Garbage attributes produce garbage access, computed confidently.&lt;/p>
&lt;p>So the dimensions you move to attributes should be the ones whose data you actually trust and keep current, usually the things sourced from the HRIS and the cloud resource tags. The dimensions you cannot keep clean are better left as explicit roles or requests, where the grant is at least visible. This is the same reason access has to be watched after it is granted regardless of model: &lt;a class="link" href="https://mattgoodrich.com/posts/telemetry-driven-access-reviews/" >usage telemetry&lt;/a>, not the role catalog, is what tells you whether the access any of these mechanisms produced is actually right.&lt;/p>
&lt;h2 id="stop-modeling-exceptions-as-roles">Stop Modeling Exceptions as Roles
&lt;/h2>&lt;p>Roles are a good tool for the part of access that is a clean function of the job, and a bad tool for everything else. The failure mode is using them for everything else anyway, one new role per exception, until the catalog is larger than the company and means nothing.&lt;/p>
&lt;p>Keep a few coarse roles for the baseline. Move the variation to attribute policies you can trust. Send the sensitive, individual grants through a request with a human on the other end. The same discipline applies whether you are scoping a person or &lt;a class="link" href="https://mattgoodrich.com/posts/agents-need-capabilities-not-roles/" >scoping an agent to capabilities&lt;/a>: the unit of access should match the shape of the decision, and most decisions are not a job title. The role catalog should stay small enough to read. The moment it is larger than your headcount, the model is no longer describing your company. It is hiding it.&lt;/p></description></item></channel></rss>