Outcome
By the end of this lesson, you will be able to identify the 15 entities in ZopNight’s RBAC policy table, map any product surface to the policy it enforces, and explain why the gateway is the single enforcement point.
| Tier | Architect |
| JTBD | ”Design a role model that lets engineers do their jobs without granting more than they need.” |
| Personas | Platform Engineer · Security/Compliance · FinOps Lead |
| Prerequisites | T1 (operator tier) for product surface awareness |
| Time | 9 minutes |
| Bloom verb | Identify (Remember), Map (Apply), Explain (Understand) |
1. Concept
ZopNight’s authorization model is built around a policy table that maps every protected endpoint to the policy a caller must hold. Every protected endpoint in the product maps to one or more entities. Every role is, fundamentally, a set of (entity, action) pairs that the role’s members are allowed to invoke.
The table is not closed, and it should not be read as a fixed list. Every capability that ships with its own surface also ships its own entity and policies: dashboards, budgets, deployment spaces, provisioning, and the two AI capabilities all arrived this way, each flowing through the same machinery as the original operational set. What is fixed is the verb set, and that is what makes the model auditable: whatever the entity, the actions are the same four, so “what can your Admin role do?” is answerable by listing entities rather than by learning per-entity vocabulary.
The core entities
ENTITY TYPICAL ACTIONS─────────────────────────────────────────────────────────resource view, create, update, deleteschedule view, create, update, deleteresource-group view, create, update, deleteoverride view, create, update, deletecloud-account view, create, update, deletenotification view, create, update, deleteteam view, create, update, deleterole view, create, update, deleteuser view, create, update, deleteorganisation view, create, update, deleteassignment view, create, update, deletestate-history viewreport viewaudit-log viewrecommendation view, create, update, delete
Shipped later, same machinery:dashboard view, create, update, deletebudget view, create, update, deletespace, provisioning view, create, update, deleteservice, deployment view, create, update, deletevirtual-key, ai-model view, create, update, delete (also scopable by AI provider)The entities cluster into three groups: operational (resource, schedule, resource-group, override, recommendation, notification), administrative (cloud-account, team, role, user, organisation, assignment), and read-only forensic (state-history, report, audit-log). The grouping matters for role design; a typical Editor role gets full operational and partial administrative; a typical Auditor role gets only the forensic group plus selective view rights.
The verb set is what is fixed
Actions are uniform: view, create, update, delete on every entity. There is no per-entity vocabulary, which is the part that matters when you design a custom role. Two consequences worth internalising:
- Applying a recommendation and dismissing one are both updates on the recommendation entity, so a role cannot be given one without the other. Separating them is a job for the remediation approval gate, not for RBAC.
- Some policies can be narrowed to a set of resources and some cannot. Organisation-management policies (role, user, assignment, organisation) are all-or-nothing by nature; the product decides which policies are resource-scopeable rather than leaving it to the role author.
The mapping from entity to backend service is internal and managed by the gateway. From the customer’s perspective the entities mirror what they see in the product: a “resource” is a thing on the Resources page, a “schedule” is a thing on the Schedules page. No mental translation required.
Policy entity coverage
Every protected endpoint in ZopNight maps to one or more policy entities. The gateway enforces this on every request, before the request reaches the backend service.
SURFACE REQUIRES─────────────────────────────────────────────────────Resources page resource:viewResource detail resource:viewStart/stop action resource:updateSchedules page schedule:viewCreate schedule schedule:createApply recommendation recommendation:updateDismiss recommendation recommendation:updateConnect cloud account cloud-account:createRotate cloud-account creds cloud-account:updateView audit log audit-log:viewExport audit log audit-log:view + report:viewInvite teammate user:createCreate custom role role:createAssign role to user role:update + assignment:createA user without the right policy gets a 403 Access Restricted from the gateway. The backend never sees the request. This is important: defense in depth means even a backend bug cannot bypass authorization.
Where the gateway enforces
The gateway sits in front of every backend service. Request flow:
client → gateway → policy check → backend service ↓ if denied: 403 (request stops here)The policy check is a single function call against the policy table, scoped to the authenticated user’s roles. The backend services do not re-check. This separation keeps the security boundary unambiguous: one component (the gateway) is responsible for authorization, and that component is hardened, audited, and changes infrequently.
Default-deny
The policy model is default-deny. If a new endpoint is added without a policy mapping, the gateway rejects all requests to it. This catches accidental over-exposure during development. The frontend will surface a policy_missing error during build-time integration tests if a new API call is made without a policy entry in the table.
How ZopNight uses the table
The policy table is defined in code, not a YAML file: the gateway’s Go PolicyTable() is the authoritative endpoint-to-policy map, and the frontend’s permissions.js mirrors it for UI gating. There is no standalone policy_table.yaml. Every change is a PR with security review, and the model holds three invariants:
- Every mutating endpoint maps to a required policy.
- Every policy uses the uniform view/create/update/delete verbs on an entity.
- The frontend’s
usePermission()helper matches the gateway’s enforcement (catches drift early).
This three-way check (service → gateway PolicyTable() → frontend permissions.js) keeps enforcement consistent. The set of entities is not a fixed 15: alongside the core resource types it also includes budget, dashboard, autoscaler-policy, event-readiness, unit-metric, and policy.
2. Demo
A team-platform engineer wants to start an EC2 instance from the Resources page. The flow:
GET /v1/resources [gateway] → resource:view → 200GET /v1/resources/i-0abc [gateway] → resource:view → 200POST /v1/resources/i-0abc/start [gateway] → resource:update → 403 ^^^ user role lacks manageIn the policy table, that endpoint is declared as:
- path: /v1/resources/{id}/start method: POST policy: { entity: resource, action: manage }The user’s effective role is Viewer. Viewer’s policy set is:
viewer: - { entity: resource, action: view } - { entity: schedule, action: view } - { entity: recommendation, action: view } - { entity: report, action: view } # ...all view-only across the 15 entitiesThe gateway resolves the request, finds no matching (resource, manage) permission, returns 403. The frontend’s usePermission('resource', 'manage') would have returned false, so the “Start” button would already be disabled, but the gateway is the actual security boundary: disabled UI is a usability nicety, not a security control.
3. Hands-on (6 min)
Open Settings → Roles in your ZopNight org. Pick any custom role (or System role for reference).
ROLE NAME: __________________
Count policies by entity: resource: _____ actions allowed schedule: _____ actions allowed recommendation: _____ actions allowed audit-log: _____ actions allowed user / role: _____ actions allowed (admin-y)
Estimated tier (Viewer / Editor / Admin): __________________
One action this role CAN do that surprises you: __________________One action this role CAN'T do that surprises you: __________________The “surprises” are usually where role design needs refinement. A FinOps Analyst that cannot dismiss recommendations is over-restricted. A junior engineer that can rotate cloud-account credentials is over-granted.
4. Knowledge check
Q1
A user clicks “Apply Recommendation” but sees Access Restricted. The most likely cause:
A. A bug in the recommendation engine
B. Their role lacks recommendation:update. Either assign a more permissive role, or grant the specific policy in a custom role. Confirm in Settings → Users → role inspector.
C. Cloud provider rejected the action
D. The recommendation expired
Show answer
Correct: B. Access Restricted is the gateway’s 403 response, which is always policy-based. The recommendation engine, cloud provider, and expiry would surface different errors. Always start a permission diagnosis at the role inspector.
Q2
The policy table is enforced where:
A. The frontend only: disabled buttons prevent action
B. The gateway, on every request. The frontend’s disabled-state is a UX nicety; the gateway is the security boundary. A scripted client that bypasses the frontend still hits the gateway and is rejected.
C. Each backend service independently
D. A WAF rule outside the application
Show answer
Correct: B. Gateway enforcement means a single, audited code path handles all authorization. Frontend gating exists to avoid showing actions the user cannot take, but it is not the security control.
Q3
A new endpoint is added without a policy entry. Default behavior of the gateway:
A. Allow all authenticated requests
B. Reject all requests (default-deny). The build pipeline also fails the integration test, catching the omission before merge.
C. Allow only Admin
D. Allow only the endpoint’s author
Show answer
Correct: B. Default-deny is the safe default and prevents accidental over-exposure. The integration test failing in CI is the second line of defense.
5. Apply
The full policy table is browsable in Settings → Roles → Policy reference. The usePermission() hook in the frontend is the canonical way for UI components to check rights before rendering. The gateway is the canonical enforcement point.
When designing a new role, start from a System role (Viewer / Editor / Admin) and remove or add specific (entity, action) pairs. The diff is the role’s defining characteristic.
Related lessons
- L2: System roles: Viewer, Editor, Admin (next)
- L3: Custom roles
- L6: Frontend gating with usePermission
Glossary terms touched
Policy entity · Gateway · Default-deny · RBAC