Skip to main content
Version: v3.9.0

Controller RBAC — operator reference

Audience: Platform operators, SREs, and integrators configuring access to Controller v3.8
Applies to: User and admin HTTP APIs under /api/v3/* (not Edgelet agent wire protocol). Includes fleet models, fleet Knowledge, RuntimeClass, microservice templates, and network topology.

Overview​

Controller uses Kubernetes-style Role and RoleBinding objects. Each HTTP route is mapped in src/config/rbac-resources.yaml to a resource and verb. At request time the RBAC middleware:

  1. Validates the OIDC bearer JWT (or allows unauthenticated routes — see below).
  2. Resolves subjects from token claims (User + Group).
  3. Looks up RoleBindings for those subjects.
  4. Evaluates Role rules against the route’s required resource and verb.

Built-in system roles are defined in src/config/rbac-system-roles.js. Custom roles are stored in the database and managed via /api/v3/roles and /api/v3/rolebindings.

Authorization flow​

Client request
→ OIDC middleware (Bearer JWT, except agent routes and public endpoints)
→ RBAC protect() middleware
→ findRouteDefinition(method, path) in rbac-resources.yaml
→ authorizer.authorize(subjects, apiGroup, resource, verb, resourceName)
→ 200 / 403

Subjects are derived from the access token (src/lib/rbac/middleware.js):

Subject kindSource claim(s)
Userpreferred_username, username, email, or sub
Groupresource_access[<OIDC_CLIENT_ID>].roles, roles[], groups[] (lowercased)

Group names should match RoleBinding subjects and IdP role names (for example admin, developer, viewer). Direct mapping of those names (no RoleBinding) and per-provider recipes: external-oidc-providers.md. Client registration: external-oidc-client-setup.md.

Password-change gate​

protect() enforces the password_change_required claim. While the claim is set, protect() allows only:

  • GET /api/v3/user/profile
  • POST /api/v3/user/change-password

Every other route that calls protect() returns 403.

Handlers in src/routes/user.js do not call protect(). /api/v3/user/* stays reachable with a bearer token. Catalog verbs on the users resource apply when a handler calls protect().

Route classes​

ClassAuthRBAC checkExamples
PublicNoneNoneGET /api/v3/live, GET /api/v3/status, GET /api/v3/architectures/
Auth-onlyBearer JWTSkipped (verbs: [] in catalog)POST /api/v3/user/login, OAuth BFF routes
User RBACBearer JWTResource + verb from catalogMost /api/v3/* admin APIs
Agent wireFog JWTSeparate from user RBAC/api/v3/agent/*

Routes with an empty verb list ([]) are catalogued for inventory but do not trigger an RBAC rule lookup; they still require authentication when the route handler is behind OIDC middleware.

Agent routes (/api/v3/agent/*) use fog provisioning tokens, not user OIDC RBAC. They are listed under the agent resource in rbac-resources.yaml for drift auditing only. agent-admin and microservice are Edgelet local API roles carried in service account tokens. See Edgelet service account roles.

System roles​

RoleScopeTypical use
adminresources: ['*'], verbs: ['*']Full cluster administration; cannot be modified or deleted
sreFull access to operational resources; read-only on roles, roleBindings, natsOperator, natsBootstrap, natsHubDay-2 operations, NATS, cluster, network topology, exec/logs
developerCRUD on workloads (microservices, applications, catalog, secrets, …); read-only on infra (fogs, router, network topology, cluster, NATS operator, system logs, …)Application developers
viewerget, list on read-only resource setRead-only dashboards
agent-adminedgelet.iofog.org/v1 *Unrestricted Edgelet local API. See Edgelet service account roles
microserviceget on four self-service resourcesDefault workload token for the Edgelet local API. See Edgelet service account roles

User API roles​

These roles authorize users and groups on /api/v3/*. admin is * on every resource and is omitted below. Each cell is the verb list from rbac-system-roles.js. — means the role does not include that resource. * is the wildcard, which covers get, list, create, update, patch, and delete.

ResourceSREDeveloperViewerNotes
microservices*get, list, create, update, patch, deleteget, list
systemMicroservices*get, listget, list
fogs*get, listget, listPOST …/reconcile is patch
applications*get, list, create, update, patch, deleteget, listReplaces legacy flows
systemApplications*get, listget, list
applicationTemplates*get, list, create, update, patch, deleteget, list
microserviceTemplates*get, list, create, update, patch, deleteget, list
services*get, list, create, update, patch, deleteget, listPOST …/reconcile is patch
router*get, listget, list
networkTopology*get, listget, listCatalog is GET-only under /api/v3/network-topology/
cluster*get, listget, listv3.8 HA controllers
natsOperator, natsBootstrap, natsHubget, listget, listget, list
natsAccounts, natsUsers, natsAccountRules, natsUserRules*get, list, create, update, patch, deleteget, listMQTT bearer create/delete is on natsUsers
catalog, registries*get, list, create, update, patch, deleteget, list
secrets, configMaps, volumeMounts, models, knowledge, runtimeClasses*get, list, create, update, patch, deleteget, listLink on volumeMounts, models, knowledge, and runtimeClasses is patch
tunnels*get, list—
certificates, capabilities*get, list, create, update, patch, deleteget, list
execSessions, logs*get, list, create, update, patch, delete—
systemExecSessions, systemLogs*get, list—
events*——
users*get, listget, listProfile is get. Password change and MFA are patch / delete. Login, refresh, logout, OAuth BFF, and interaction routes use verbs: []. src/routes/user.js does not call protect()
authUsers, authGroups*get, listget, listEmbedded identity admin
config*get, listget, list
controller*get, list, create, update, patch, deleteget, listRoutes are public (verbs: []): /live, /status, /architectures/. protect() skips the role check
roles, roleBindingsget, listget, listget, listMutations require admin or a custom role
serviceAccounts*get, list, create, update, patch, deleteget, listController API for ServiceAccount objects. Workload tokens use the Edgelet roles below
authAdmin———admin only. JWKS rotate, auth migration
agent———Fog token on /api/v3/agent/*. Listed for drift auditing. not user RBAC

Edgelet service account roles​

agent-admin and microservice authorize calls from a running microservice to the Edgelet local API on its node. They do not authorize /api/v3/*.

A microservice sets serviceAccount.roleRef to a Role in API group edgelet.iofog.org/v1. On GET /api/v3/agent/microservices, Controller resolves that role and returns serviceAccount.rules (src/services/agent-service.js). Edgelet uses those rules to mint the service account token it mounts into the container. The workload presents that token to the Edgelet local API.

RoleAPI groupResourceVerbsWhat the workload can call
agent-adminedgelet.iofog.org/v1**Every local API
microserviceedgelet.iofog.org/v1microservices/config/selfgetThis workload's own config
microserviceedgelet.iofog.org/v1auth/whoamigetIdentity of the calling workload
microserviceedgelet.iofog.org/v1system/gpsgetNode GPS
microserviceedgelet.iofog.org/v1microservices/control/selfgetThis workload's own control endpoint

microservice is the default self-service role. agent-admin is the unrestricted local API role. A custom Role in edgelet.iofog.org/v1 is delivered the same way when serviceAccount.roleRef names it.

Bind system roles to users or groups with RoleBindings, for example:

apiVersion: datasance.com/v3
kind: RoleBinding
metadata:
name: alice-developer
subjects:
- kind: User
roleRef:
kind: Role
name: developer

Verbs​

Standard verbs match Kubernetes conventions:

VerbTypical HTTP methods
getGET single resource, HEAD
listGET collection
createPOST
updatePUT
patchPATCH, some POST sub-actions
deleteDELETE

Some sub-resource actions map to patch (for example microservice start/stop). WebSocket routes use verb get with method WS.

v3.8 RBAC changes​

v3.7 / legacyv3.8
RBAC resource flowsapplications / systemApplications
GET /api/v3/fog-typesGET /api/v3/architectures/ (public)
EdgeResource, diagnostics, straceRemoved — no yaml entries
POST /api/v3/agent/controller/registerAdded under agent resource (fog token)
OIDC auth routes (OAuth BFF, interactions)Catalogued under users with auth-only verbs ([]). Profile, change-password, and MFA on the same resource use get, patch, and delete

Fleet models, fleet Knowledge, RuntimeClass, and microservice templates are user RBAC resources (models, knowledge, runtimeClasses, microserviceTemplates). Viewer gets get/list. SRE, developer, and admin get full CRUD including link (patch on …/:name/link). Agent GET /api/v3/agent/models, GET /api/v3/agent/knowledge, and GET /api/v3/agent/runtimeClasses are fog-token routes catalogued under agent for drift auditing only. Catalog PATCH /api/v3/microservices/:uuid/models and PATCH /api/v3/microservices/:uuid/knowledge stay on the microservices resource.

Network topology is the networkTopology resource. The catalog is GET-only under /api/v3/network-topology/ (summary, router and NATS overview, nodes, connections, subgraph). Collection and subgraph routes use list. A single node and its connections use get. SRE has *. Developer and viewer have get and list. Custom roles use the resource name networkTopology.

Fog platform retry (POST /api/v3/iofog/:uuid/reconcile) is patch on fogs. Service retry (POST /api/v3/services/:name/reconcile) is patch on services. MQTT bearer create and delete stay on natsUsers (create / delete).

Removed user HAL/USB inventory routes (GET /api/v3/iofog/:uuid/hal/hw, GET /api/v3/iofog/:uuid/hal/usb) and agent HAL PUT routes have no yaml entries.

Orphan RBAC entries for removed APIs must not reappear. CI and local checks enforce this (see Maintenance).

Custom roles​

Operators can define additional Roles via API or YAML:

  • GET/POST /api/v3/roles, GET/PATCH/DELETE /api/v3/roles/:name
  • GET/POST /api/v3/rolebindings, GET/PATCH/DELETE /api/v3/rolebindings/:name

Custom roles use apiVersion: datasance.com/v3 and the same resource names as the route catalog. The admin system role always wins via * rules.

Maintenance and drift checks​

Keep rbac-resources.yaml, live routes, and system roles aligned when adding or removing APIs.

nvm use 24

# Compare Express routes to rbac-resources.yaml
npm run rbac-audit

# grep gates — banned legacy terms must be absent;
# v3.8 terms must be present
rg 'edgeResources|diagnostics' src/config/rbac-resources.yaml && exit 1 || true
rg 'fog-types' src/config/rbac-resources.yaml && exit 1 || true
rg 'architectures|controller/register' src/config/rbac-resources.yaml

npm run rbac-audit exits non-zero on:

  • Live routes missing from the yaml catalog (gaps)
  • Yaml entries with no matching route (orphans)
  • Banned legacy terms (edgeResources, diagnostics, fog-types)
  • Missing required v3.8 terms (architectures, controller/register)

Optional CI wiring is planned.

Reference files​

TopicPath
Route → resource catalogsrc/config/rbac-resources.yaml
System rolessrc/config/rbac-system-roles.js
RBAC middlewaresrc/lib/rbac/middleware.js
Authorizersrc/lib/rbac/authorizer.js
Route inventory (generated)node scripts/route-inventory.js
Drift scriptscripts/rbac-audit.js, npm run rbac-audit
External IdP clientexternal-oidc-client-setup.md
External IdP groups and providersexternal-oidc-providers.md
HTTP API specdocs/swagger.yaml